Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 激情五月色婷婷丁香伊人,亚洲精品一区二区三区人妖,国产特黄特色a级在线视频

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          三個繪圖工具類詳解

          .相關方法詳解


          1)Paint(畫筆):

          就是畫筆,用于設置繪制風格,如:線寬(筆觸粗細),顏色,透明度和填充風格等 直接使用無參構造方法就可以創建Paint實例: Paint paint = new Paint( );

          我們可以通過下述方法來設置Paint(畫筆)的相關屬性,另外,關于這個屬性有兩種, 圖形繪制相關與文本繪制相關:

          • setARGB(int a,int r,int g,int b): 設置繪制的顏色,a代表透明度,r,g,b代表顏色值。
          • setAlpha(int a): 設置繪制圖形的透明度。
          • setColor(int color): 設置繪制的顏色,使用顏色值來表示,該顏色值包括透明度和RGB顏色。
          • setAntiAlias(boolean aa): 設置是否使用抗鋸齒功能,會消耗較大資源,繪制圖形速度會變慢。
          • setDither(boolean dither): 設定是否使用圖像抖動處理,會使繪制出來的圖片顏色更加平滑和飽滿,圖像更加清晰
          • setFilterBitmap(boolean filter): 如果該項設置為true,則圖像在動畫進行中會濾掉對Bitmap圖像的優化操作, 加快顯示速度,本設置項依賴于dither和xfermode的設置
          • setMaskFilter(MaskFilter maskfilter): 設置MaskFilter,可以用不同的MaskFilter實現濾鏡的效果,如濾化,立體等
          • setColorFilter(ColorFilter colorfilter): 設置顏色過濾器,可以在繪制顏色時實現不用顏色的變換效果
          • setPathEffect(PathEffect effect) 設置繪制路徑的效果,如點畫線等
          • setShader(Shader shader): 設置圖像效果,使用Shader可以繪制出各種漸變效果
          • setShadowLayer(float radius ,float dx,float dy,int color):在圖形下面設置陰影層,產生陰影效果, radius為陰影的角度,dx和dy為陰影在x軸和y軸上的距離,color為陰影的顏色
          • setStyle(Paint.Style style): 設置畫筆的樣式,為FILL,FILL_OR_STROKE,或STROKE
          • setStrokeCap(Paint.Cap cap): 當畫筆樣式為STROKE或FILL_OR_STROKE時,設置筆刷的圖形樣式, 如圓形樣Cap.ROUND,或方形樣式Cap.SQUARE
          • setSrokeJoin(Paint.Join join): 設置繪制時各圖形的結合方式,如平滑效果等
          • setStrokeWidth(float width): 當畫筆樣式為STROKE或FILL_OR_STROKE時,設置筆刷的粗細度
          • setXfermode(Xfermode xfermode): 設置圖形重疊時的處理方式,如合并,取交集或并集,經常用來制作橡皮的擦除效果
          • setFakeBoldText(boolean fakeBoldText): 模擬實現粗體文字,設置在小字體上效果會非常差
          • setSubpixelText(boolean subpixelText): 設置該項為true,將有助于文本在LCD屏幕上的顯示效果
          • setTextAlign(Paint.Align align): 設置繪制文字的對齊方向
          • setTextScaleX(float scaleX): 設置繪制文字x軸的縮放比例,可以實現文字的拉伸的效果
          • setTextSize(float textSize): 設置繪制文字的字號大小
          • setTextSkewX(float skewX): 設置斜體文字,skewX為傾斜弧度
          • setTypeface(Typeface typeface): 設置Typeface對象,即字體風格,包括粗體,斜體以及襯線體,非襯線體等
          • setUnderlineText(boolean underlineText): 設置帶有下劃線的文字效果
          • setStrikeThruText(boolean strikeThruText): 設置帶有刪除線的效果
          • setStrokeJoin(Paint.Join join): 設置結合處的樣子,Miter:結合處為銳角, Round:結合處為圓弧:BEVEL:結合處為直線
          • setStrokeMiter(float miter):設置畫筆傾斜度
          • setStrokeCap (Paint.Cap cap):設置轉彎處的風格 其他常用方法:
          • float ascent( ):測量baseline之上至字符最高處的距離


          • float descent():baseline之下至字符最低處的距離
          • int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth): 檢測一行顯示多少文字
          • clearShadowLayer( ):清除陰影層 其他的自行查閱文檔~

          2)Canvas(畫布):

          畫筆有了,接著就到畫布(Canvas),總不能憑空作畫是吧~常用方法如下:

          首先是構造方法,Canvas的構造方法有兩種:

          Canvas(): 創建一個空的畫布,可以使用setBitmap()方法來設置繪制具體的畫布。

          Canvas(Bitmap bitmap): 以bitmap對象創建一個畫布,將內容都繪制在bitmap上,因此bitmap不得為null。

          接著是 1.drawXXX()方法族:以一定的坐標值在當前畫圖區域畫圖,另外圖層會疊加, 即后面繪畫的圖層會覆蓋前面繪畫的圖層。 比如:

          • drawRect(RectF rect, Paint paint) :繪制區域,參數一為RectF一個區域
          • drawPath(Path path, Paint paint) :繪制一個路徑,參數一為Path路徑對象
          • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) : 貼圖,參數一就是我們常規的Bitmap對象,參數二是源區域(這里是bitmap), 參數三是目標區域(應該在canvas的位置和大小),參數四是Paint畫刷對象, 因為用到了縮放和拉伸的可能,當原始Rect不等于目標Rect時性能將會有大幅損失。
          • drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) : 畫線,參數一起始點的x軸位置,參數二起始點的y軸位置,參數三終點的x軸水平位置, 參數四y軸垂直位置,最后一個參數為Paint 畫刷對象。
          • drawPoint(float x, float y, Paint paint): 畫點,參數一水平x軸,參數二垂直y軸,第三個參數為Paint對象。
          • drawText(String text, float x, floaty, Paint paint) : 渲染文本,Canvas類除了上面的還可以描繪文字,參數一是String類型的文本, 參數二x軸,參數三y軸,參數四是Paint對象。
          • drawOval(RectF oval, Paint paint):畫橢圓,參數一是掃描區域,參數二為paint對象;
          • drawCircle(float cx, float cy, float radius,Paint paint): 繪制圓,參數一是中心點的x軸,參數二是中心點的y軸,參數三是半徑,參數四是paint對象;
          • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint): 畫弧,參數一是RectF對象,一個矩形區域橢圓形的界限用于定義在形狀、大小、電弧,參數二是起始角 (度)在電弧的開始,參數三掃描角(度)開始順時針測量的,參數四是如果這是真的話,包括橢圓中心的電 弧,并關閉它,如果它是假這將是一個弧線,參數五是Paint對象;

          2.clipXXX()方法族:在當前的畫圖區域裁剪(clip)出一個新的畫圖區域,這個畫圖區域就是canvas 對象的當前畫圖區域了。比如:clipRect(new Rect()),那么該矩形區域就是canvas的當前畫圖區域

          3.save()restore()方法: save( ):用來保存Canvas的狀態。save之后,可以調用Canvas的平移、放縮、旋轉、錯切、裁剪等操作! restore():用來恢復Canvas之前保存的狀態。防止save后對Canvas執行的操作對后續的繪制有影響。 save()和restore()要配對使用(restore可以比save少,但不能多),若restore調用次數比save多,會報錯!

          4.translate(float dx, float dy): 平移,將畫布的坐標原點向左右方向移動x,向上下方向移動y.canvas的默認位置是在(0,0)

          5.scale(float sx, float sy):擴大,x為水平方向的放大倍數,y為豎直方向的放大倍數

          6.rotate(float degrees):旋轉,angle指旋轉的角度,順時針旋轉


          3)Path(路徑)

          簡單點說就是描點,連線~在創建好我們的Path路徑后,可以調用Canvas的drawPath(path,paint) 將圖形繪制出來~常用方法如下:

          • addArc(RectF oval, float startAngle, float sweepAngle:為路徑添加一個多邊形
          • addCircle(float x, float y, float radius, Path.Direction dir):給path添加圓圈
          • addOval(RectF oval, Path.Direction dir):添加橢圓形
          • addRect(RectF rect, Path.Direction dir):添加一個區域
          • addRoundRect(RectF rect, float[] radii, Path.Direction dir):添加一個圓角區域
          • isEmpty():判斷路徑是否為空
          • transform(Matrix matrix):應用矩陣變換
          • transform(Matrix matrix, Path dst):應用矩陣變換并將結果放到新的路徑中,即第二個參數。

          更高級的效果可以使用PathEffect類!

          幾個To:

          • moveTo(float x, float y):不會進行繪制,只用于移動移動畫筆
          • lineTo(float x, float y):用于直線繪制,默認從(0,0)開始繪制,用moveTo移動! 比如 mPath.lineTo(300, 300); canvas.drawPath(mPath, mPaint);
          • quadTo(float x1, float y1, float x2, float y2): 用于繪制圓滑曲線,即貝塞爾曲線,同樣可以結合moveTo使用!


          • rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 同樣是用來實現貝塞爾曲線的。 (x1,y1) 為控制點,(x2,y2)為控制點,(x3,y3) 為結束點。 Same as cubicTo, but the coordinates are considered relative to the current point on this contour.就是多一個控制點而已~ 繪制上述的曲線: mPath.moveTo(100, 500); mPath.cubicTo(100, 500, 300, 100, 600, 500); 如果不加上面的那個moveTo的話:則以(0,0)為起點,(100,500)和(300,100)為控制點繪制貝塞爾曲線


          • arcTo(RectF oval, float startAngle, float sweepAngle): 繪制弧線(實際是截取圓或橢圓的一部分)ovalRectF為橢圓的矩形,startAngle 為開始角度, sweepAngle 為結束角度。

          2.動手試試:

          屬性那么多,肯定要手把手的擼一下,才能加深我們的映像是吧~ 嘿嘿,畫圖要么在View上畫,要么在SurfaceView上畫,這里我們在View上畫吧, 我們定義一個View類,然后再onDraw()里完成繪制工作!

          /**

          * Created by Jay on 2015/10/15 0015.

          */

          public class MyView extends View{

          private Paint mPaint;

          public MyView(Context context) {

          super(context);

          init();

          }

          public MyView(Context context, AttributeSet attrs) {

          super(context, attrs);

          init();

          }

          public MyView(Context context, AttributeSet attrs, int defStyleAttr) {

          super(context, attrs, defStyleAttr);

          init();

          }

          private void init(){

          mPaint = new Paint();

          mPaint.setAntiAlias(true); //抗鋸齒

          mPaint.setColor(getResources().getColor(R.color.puple));//畫筆顏色

          mPaint.setStyle(Paint.Style.FILL); //畫筆風格

          mPaint.setTextSize(36); //繪制文字大小,單位px

          mPaint.setStrokeWidth(5); //畫筆粗細

          }

          //重寫該方法,在這里繪圖

          @Override

          protected void onDraw(Canvas canvas) {

          super.onDraw(canvas);

          }

          }

          然后布局那里設置下這個View就好,下述代碼都寫在onDrawable中~


          1)設置畫布顏色:

          canvas.drawColor(getResources().getColor(R.color.yellow)); //設置畫布背景顏色


          2)繪制圓形:

          canvas.drawCircle(200, 200, 100, mPaint); //畫實心圓


          3)繪制矩形:

          canvas.drawRect(0, 0, 200, 100, mPaint); //畫矩形


          4)繪制Bitmap:

          canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 0, 0, mPaint);


          5)繪制弧形區域:

          canvas.drawArc(new RectF(0, 0, 100, 100),0,90,true,mPaint); //繪制弧形區域

          假如true改為false:


          6)繪制圓角矩形

          canvas.drawRoundRect(new RectF(10,10,210,110),15,15,mPaint); //畫圓角矩形


          7)繪制橢圓

          canvas.drawOval(new RectF(0,0,200,300),mPaint); //畫橢圓


          8)繪制多邊形:

          Path path = new Path();

          path.moveTo(10, 10); //移動到 坐標10,10

          path.lineTo(100, 50);

          path.lineTo(200,40);

          path.lineTo(300, 20);

          path.lineTo(200, 10);

          path.lineTo(100, 70);

          path.lineTo(50, 40);

          path.close();

          canvas.drawPath(path,mPaint);


          9)繪制文字:

          canvas.drawText("最喜歡看曹神日狗了~",50,50,mPaint); //繪制文字

          你也可以沿著某條Path來繪制這些文字:

          Path path = new Path();

          path.moveTo(50,50);

          path.lineTo(100, 100);

          path.lineTo(200, 200);

          path.lineTo(300, 300);

          path.close();

          canvas.drawTextOnPath("最喜歡看曹神日狗了~", path, 50, 50, mPaint); //繪制文字


          10)繪制自定義的圖形:

          代碼來源于網上:

          protected void onDraw(Canvas canvas) {

          super.onDraw(canvas);

          canvas.translate(canvas.getWidth()/2, 200); //將位置移動畫紙的坐標點:150,150

          canvas.drawCircle(0, 0, 100, mPaint); //畫圓圈

          //使用path繪制路徑文字

          canvas.save();

          canvas.translate(-75, -75);

          Path path = new Path();

          path.addArc(new RectF(0,0,150,150), -180, 180);

          Paint citePaint = new Paint(mPaint);

          citePaint.setTextSize(14);

          citePaint.setStrokeWidth(1);

          canvas.drawTextOnPath("繪制表盤~", path, 28, 0, citePaint);

          canvas.restore();

          Paint tmpPaint = new Paint(mPaint); //小刻度畫筆對象

          tmpPaint.setStrokeWidth(1);

          float y=100;

          int count = 60; //總刻度數

          for(int i=0 ; i <count ; i++){

          if(i%5 == 0){

          canvas.drawLine(0f, y, 0, y+12f, mPaint);

          canvas.drawText(String.valueOf(i/5+1), -4f, y+25f, tmpPaint);

          }else{

          canvas.drawLine(0f, y, 0f, y +5f, tmpPaint);

          }

          canvas.rotate(360/count,0f,0f); //旋轉畫紙

          }

          //繪制指針

          tmpPaint.setColor(Color.GRAY);

          tmpPaint.setStrokeWidth(4);

          canvas.drawCircle(0, 0, 7, tmpPaint);

          tmpPaint.setStyle(Paint.Style.FILL);

          tmpPaint.setColor(Color.YELLOW);

          canvas.drawCircle(0, 0, 5, tmpPaint);

          canvas.drawLine(0, 10, 0, -65, mPaint);

          }


          本節小結:

          本節我們對android.graphics接口類下的三個繪圖API:Canvas(畫布),Paint(畫筆),Path(路徑)進行 了學習,方法有很多,別去死記,用到的時候查就好,這里我們先有個大概映像即可,自定義控件那里 我們再來慢慢糾結~好的,就說這么多

          UI組件化對項目有正向收益,不僅能提效,還能保證高度的視覺還原度,減少和UI設計師溝通成本,所以也得到了大家的認可。

          所以每個項目都會啟動UI組件化建設,但是UI視圖是和項目強相關的,項目間無法復用,導致大家疲于實現,重復造輪子,拖延下班時間,那么基于上面的背景,有沒有更好的解決方案呢,答案是有的,下面介紹一下UI組件化在項目中的實施經驗,下面分為目標工程架構組件架構組件實現來展開。

          目標

          對現有UI組件化進行容器化抽象,底層UI組件提供最大功能集合,完全解耦業務邏輯,業務方根據自己需求,基于基礎組件開發,通過屬性配置或者組合的方式達到復雜的效果,所以只要底層組件抽象的足夠好、能力足夠全,就能大大的提高開發效率,后期適配也不會涉及核心邏輯修改,一定程度的保證了功能的穩定性

          工程架構

          module劃分

          所有的ui組件統一收斂到uikit下,其下面moudle劃分以是否非常通用為依據,如果非業務屬性,并且特別通用的模塊組件,抽取單獨module,方便解耦和復用,如果不是則統一放在同一個module下,這樣uikit模塊劃分如下:

          • app

          空殼工程,可以單獨運行

          • demo

          對所有的組件提供demo,里面的功能也可以在調試面板中打開

          • uikit

          依賴widget和module,業務使用ui組件直接依賴uikit即可

          • widgetDivideLine
          • XRadioGroup
          • LoadingView
          • ShimmerLayout
          • ...
          • uikit-moduleflatButton
          • roundView
          • load
          • dialog
          • imageSelect
          • toast
          • ...

          工程分層

          工程架構可以分為5層,分別是:基礎控件、組合控件、業務UI組件、橋接、demo。

          • 基礎控件:提供原子能力,單點控件,比如層疊布局FlowLayout、骨架控件ShimmerLayout、按鈕Flatbutton
          • 組合控件:會依賴基礎控件,比如Dialog、ImageSelector,這些控件UI會比較復雜,所以會用到FlatButton、ShimmerLayout等基礎組件來提高開發效率
          • 業務UI組件:這個就是我們真正要實現的UI組件,基于設計要求定制開發,在基礎控件和組合控件上配置業務偏好,組合成業務組件,開發工作量比較小
          • 橋接:業務層不感知UI組件的個數和依賴關系,業務層只依賴uikit,而UI組件的依賴管理收斂到uikit中,這樣的好處就是,后續迭代只在uikit維護依賴關系即可
          • Demo: 一個好的組件除了使用文檔,還需要有直觀的示例代碼,demo可以直接集成到調試面板中

          架構分層如下圖:

          一個好的架構應該層次分明、低耦合、高擴展,對組件的增刪支持的足夠友好,任何組件都能準確的找到對應的分層,并且不會改動到已有代碼,所以review一下剛剛設計的架構,基本上滿足需求,架構設計符合預期。

          架構設計好之后的步驟就是實施了,如何和現有的工程做結合呢,UI組件按階段可以分為:開發階段、穩定階段,理想的開發模式為開發階段在宿主工程中開發調試,但是放宿主工程中會帶了編譯慢的問題,組件開發和業務是接耦的,所以希望代碼在宿主工程,demo和組件開發可以單獨運行,當組件開發完成,到了穩定階段,組件代碼修改頻率降低,同時加快編譯速度,UIKit組件發布到遠程maven倉庫,最終uikit工程獨立出來,單獨迭代,下面是工程架構實現

          UI組件和宿主打包編譯

          settings.gradle

          includeIfAbsent ':uikit:uikit'
          includeIfAbsent ':uikit:demo'
          includeIfAbsent ':uikit:imgselector'
          includeIfAbsent ':uikit:roundview'
          includeIfAbsent ':uikit:widget'
          includeIfAbsent ':uikit:photodraweeview'
          includeIfAbsent ':uikit:flatbutton'
          includeIfAbsent ':uikit:dialog'
          includeIfAbsent ':uikit:widgetlayout'
          includeIfAbsent ':uikit:statusbar'
          includeIfAbsent ':uikit:toolbar'
          復制代碼
          

          common_business.gradle中一鍵依賴

          apply from: rootProject.file("library_base.gradle")
          
          dependencies {
              ...
              implementation project(":uikit:uikit")
          }
          復制代碼
          

          UI組件獨立編譯

          uikit/shell/settings.gradle

          include ':app'
          includeModule('widget','../')
          includeModule('demo','../')
          includeModule('flatbutton','../')
          includeModule('imgselector','../')
          includeModule('photodraweeview','../')
          includeModule('roundview','../')
          includeModule('uikit','../')
          includeModule('widgetlayout','../')
          includeModule('dialog','../')
          includeModule('statusbar','../')
          includeModule('toolbar','../')
          
          def includeModule(name, filePath = name) {
              def projectDir = new File(filePath+name)
              if (projectDir.exists()) {
                  include ':uikit:' + name
                  project(':uikit:' + name).projectDir = projectDir
              } else {
                  print("settings:could not find module $name in path $filePath")
              }
          }
          復制代碼
          

          UI組件lib的build.gradle中

          if (rootProject.ext.is_in_uikit_project) {
              apply from: rootProject.file('../uikit.gradle')
          } else {
              apply from: rootProject.file('uikit/uikit.gradle')
          }
          復制代碼
          

          這樣就實現了宿主工程UIKit代碼單獨運行的效果了

          組件架構

          組件可以分為2類:工具型、業務類型,2個類型的組件迭代思路差異非常的大,工具型組件,只要單點做到極致就ok了,整體比較簡單,復用性也比較強,而業務型組件就會稍顯復雜,既要考慮復用性,也要考慮可擴展性,下面分別介紹這2個類型組件的實現思路

          工具型

          工具型組件迭代的思路就是不斷的完善基礎能力,盡可能的功能全面,在已有的能力上不斷的支持新的功能,比較重要的就是兼容已有api,比較代表性的組件有FlatButton、RoundView、StatusBar,可以參考下FlatButton&RoundView迭代歷程:

          業務型

          如何做好一個業務組件呢,實現可以是具象的,也可以是抽象的,好的組件設計應該是2者兼備,最底層的實現應該是足夠抽象,而上層實現又應該是具象的,所以需要帶著容器化的思路來實現,那么怎么個思路呢,如下圖:

          組件實現

          下面以FlatButton為例介紹組件實現方式,其它組件實現思路類似。在實現前,我們先看下視覺稿

          按鈕樣式特別多,實現方式也可以有很多種,現有工程也給出了實現方案,具體如下:

          第一步:分別定義noraml下的shape和pressed的shape,如果enable = false,還得再定義一個dissable的shape

          normal (ui_standard_bg_btn_corner_28_ripple)

          <?xml version="1.0" encoding="utf-8"?>
          <ripple xmlns:android="http://schemas.android.com/apk/res/android"
              android:color="@color/button_pressed_cover">
              <item
                  android:drawable="@drawable/ui_standard_bg_btn_corner_28_enable">
              </item>
          
          </ripple>
          復制代碼
          

          pressed(ui_standard_bg_btn_corner_28_disable)

          <?xml version="1.0" encoding="utf-8"?>
          <shape xmlns:android="http://schemas.android.com/apk/res/android"
              android:shape="rectangle">
              <gradient
                  android:angle="0"
                  android:endColor="@color/button_disable_end"
                  android:startColor="@color/button_disable_start"
                  android:useLevel="false"
                  android:type="linear" />
              <corners android:radius="28dp" />
          </shape>
          復制代碼
          

          第二步:定義selector

          selector(ui_standard_bg_btn_corner_28)

          <?xml version="1.0" encoding="utf-8"?>
          <selector xmlns:android="http://schemas.android.com/apk/res/android">
              <item android:state_enabled="true" android:drawable="@drawable/ui_standard_bg_btn_corner_28_ripple" />
              <item android:state_enabled="false" android:drawable="@drawable/ui_standard_bg_btn_corner_28_disable" />
          </selector>
          復制代碼
          

          第三步:使用

          <TextView
              ...
              android:background="@drawable/ui_standard_bg_btn_corner_28"
              android:textColor="@color/white"/>
          復制代碼
          

          這樣按鈕的背景按壓就實現了,如果在此基礎上,文字也需要按壓態,那么就重復上面的步驟,對顏色再創建一個選擇器,當實現完上面UI定義的樣式后,工程中的畫風如下:

          我是誰,我在哪里,這該怎么玩,長得都差不多,基本沒有開發體驗,復用性、擴展性都非常的差,如果來個UI大改版,又得從頭再來一次。那怎么解決上面的問題呢,答案是定義按鈕通用能力,業務上層再實現,按這個思路做,需要刪除上面所有shape、selector,然后自定義控件,我們都知道,上面定義的shape、selector xml文件,android系統最終都是會解析生成對應的對象,所以我們借鑒一下系統代碼,實現起來就so easy

          看下這個shape xml

          <shape xmlns:android="http://schemas.android.com/apk/res/android"
              android:shape="rectangle">
              <gradient
                  android:angle="0"
                  android:endColor="@color/button_disable_end"
                  android:startColor="@color/button_disable_start"
                  android:useLevel="false"
                  android:type="linear" />
              <corners android:radius="28dp" />
          </shape>
          復制代碼
          

          解析后的對象為GradientDrawable

          public void setOrientation(Orientation orientation)
          public void setColors(@Nullable @ColorInt int[] colors)
          public void setCornerRadii(@Nullable float[] radii)
          public void setStroke(int width, @ColorInt int color)
          ...
          復制代碼
          

          也就是說,xml中定義的屬性,代碼中都可以實現,除了GradientDrawable,還會用到RippleDrawable實現水波紋,同理文字顏色選擇器代碼中對應的為ColorStateList,有了上面鋪墊,具體實現如下:

          第一步:定義自定義屬性

          <declare-styleable name="FlatButton">
              <!--默認背景顏色 -->
              <attr name="fb_colorNormal" format="color" />
              <!--按下背景顏色 -->
              <attr name="fb_colorPressed" format="color" />
              <!--Disable背景顏色 -->
              <attr name="fb_colorDisable" format="color" />
              <!--默認開始漸變顏色 -->
              <attr name="fb_colorNormalStart" format="color" />
              <!--默認結束漸變顏色 -->
              <attr name="fb_colorNormalEnd" format="color" />
              <!--按下開始漸變顏色 -->
              <attr name="fb_colorPressedStart" format="color" />
              <!--按下結束漸變顏色 -->
              <attr name="fb_colorPressedEnd" format="color" />
              <!--Disable開始漸變顏色 -->
              <attr name="fb_colorDisableStart" format="color" />
              <!--Disable結束漸變顏色 -->
              <attr name="fb_colorDisableEnd" format="color" />
              <!--漸變方向 -->
              <attr name="fb_gradientOrientation">
                  <enum name="left_right" value="0" />
                  <enum name="right_left" value="1" />
                  <enum name="top_bottom" value="2" />
                  <enum name="bottom_top" value="3" />
                  <enum name="tr_bl" value="4" />
                  <enum name="bl_tr" value="5" />
                  <enum name="br_tl" value="6" />
                  <enum name="tl_br" value="7" />
              </attr>
          
              <!--默認文字顏色 -->
              <attr name="fb_colorNormalText" format="color" />
              <!--按下文字顏色 -->
              <attr name="fb_colorPressedText" format="color" />
              <!--Disable文字顏色 -->
              <attr name="fb_colorDisableText" format="color" />
          
              <!--邊框顏色 -->
              <attr name="fb_strokeColor" format="color" />
              <!--按下邊框顏色 -->
              <attr name="fb_strokePressColor" format="color" />
              <!--Disable邊框顏色 -->
              <attr name="fb_strokeDisableColor" format="color" />
              <!--邊框寬度 -->
              <attr name="fb_strokeWidth" format="dimension" />
          
              <!--水波紋是否可用 -->
              <attr name="fb_isRippleEnable" format="boolean" />
              <!--默認水波紋顏色 -->
              <attr name="fb_colorRippleNormal" format="color" />
              <!--按下水波紋顏色 -->
              <attr name="fb_colorRipplePressed" format="color" />
          
              <!--圓角角度 -->
              <attr name="fb_cornerRadius" format="dimension" />
              <!--左上圓角角度 -->
              <attr name="fb_radius_TL" format="dimension" />
              <!--右上圓角角度 -->
              <attr name="fb_radius_TR" format="dimension" />
              <!--左下圓角角度 -->
              <attr name="fb_radius_BL" format="dimension" />
              <!--右下圓角角度 -->
              <attr name="fb_radius_BR" format="dimension" />
          
              <!--是否開啟防抖 -->
              <attr name="fb_antiShakeEnable" format="boolean" />
          </declare-styleable>
          復制代碼
          

          第二步:核心實現邏輯

          private fun setBackgroundCompat() {
              val stateListDrawable = createStateListDrawable()
              val pL = paddingLeft
              val pT = paddingTop
              val pR = paddingRight
              val pB = paddingBottom
              background = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isRippleEnable) {
                  val rippleDrawable = RippleDrawable(createRippleColorStateList(), stateListDrawable, null)
                  rippleDrawable
              } else {
                  stateListDrawable
              }
              setPadding(pL, pT, pR, pB)
          }
          
          private fun createStateListDrawable(): StateListDrawable {
              var normalDrawable = StateListDrawable()
              normalDrawable.addState(
                      intArrayOf(android.R.attr.state_pressed),
                      createPressedDrawable()
              )
              normalDrawable.addState(
                      intArrayOf(android.R.attr.state_focused),
                      createPressedDrawable()
              )
              normalDrawable.addState(
                      intArrayOf(-android.R.attr.state_enabled),
                      createDisableDrawable()
              )
              normalDrawable.addState(
                      intArrayOf(android.R.attr.state_selected),
                      createPressedDrawable()
              )
              normalDrawable.addState(intArrayOf(), createNormalDrawable())
              return normalDrawable
          }
          
          private fun createRippleColorStateList(): ColorStateList {
              val stateList = arrayOf(intArrayOf(android.R.attr.state_pressed), intArrayOf(android.R.attr.state_focused), intArrayOf(android.R.attr.state_activated), intArrayOf())
              val normalColor = backgroundStyle.getColorRippleNormalFallback()
              val pressedColor = backgroundStyle.getColorRipplePressedFallback()
              val stateColorList = intArrayOf(
                      pressedColor,
                      pressedColor,
                      pressedColor,
                      normalColor
              )
              return ColorStateList(stateList, stateColorList)
          }
          復制代碼
          

          第三步:UI組件實現

          xml中使用

          <com.snapsolve.uikit.flatbutton.FlatButton
              app:fb_colorNormalText="@color/uikit_color_white"
              app:fb_colorPressedText="@color/uikit_color_white"
              app:fb_colorNormalEnd="#FF9800"
              app:fb_colorNormalStart="#FF0000"
              app:fb_colorPressedEnd="#4CAF50"
              app:fb_colorPressedStart="#009688"
              app:fb_colorRippleNormal="#303F9F"
              app:fb_colorRipplePressed="#FF4081"
              app:fb_cornerRadius="24dp"
              app:fb_gradientOrientation="left_right"
              app:fb_isRippleEnable="true" 
              ...
              />
          復制代碼
          

          代碼中使用

          fb_radius_in_code.setBackgroundStyle {
              this.colorNormal = resources.getColor(R.color.uikit_color_FF4081)
              this.colorPressed = resources.getColor(R.color.uikit_color_9C27B0)
              this.colorRippleNormal = resources.getColor(R.color.uikit_color_FF4081)
              this.colorRipplePressed = resources.getColor(R.color.uikit_color_9C27B0)
          }.setRadiusStyle {
              this.radiusTL = dp2px(24F)
              this.radius_BR = dp2px(24F)
          }
          復制代碼
          

          到這里,底層Button能力定義完成,接下來就是組件化實現了,具體實現方式如下:

          無法復制加載中的內容

          項目中的按鈕UI按照UI組件要求,可以基于FlatButton來實現,配置好給種類型的屬性,按鈕名字可以和設計對齊,到這里就基本完成了

          第四步:業務使用

          一級按鈕、二級按鈕、三級按鈕的實現可以通過繼承FlatButton,設置默認樣式,使用的時候就不需要再在xml中定義任何屬性,只需記住組件名字,依賴即可,做到真正的開箱即用

          舉一個例子,定義一個線框button

          class StrokeButton : FlatButton {
              constructor(context: Context) : this(context, null)
              constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
              constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
                  config(context, attrs)
              }
          
              private fun config(context: Context, attrs: AttributeSet?){
                  .setBackgroundStyle {
                      this.colorNormal = resources.getColor(R.color.uikit_color_FF4081)
                      this.colorPressed = resources.getColor(R.color.uikit_color_9C27B0)
                      this.colorRippleNormal = resources.getColor(R.color.uikit_color_FF4081)
                      this.colorRipplePressed = resources.getColor(R.color.uikit_color_9C27B0)
                  }.setRadiusStyle {
                      this.radiusTL = dp2px(28F)
                      this.radius_BR = dp2px(28F)
                  }
              }
              private fun dp2px(dp: Float): Float {
                  return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
              }
          }
          復制代碼
          

          業務使用

          .相關方法詳解


          1)Paint(畫筆):

          就是畫筆,用于設置繪制風格,如:線寬(筆觸粗細),顏色,透明度和填充風格等 直接使用無參構造方法就可以創建Paint實例: Paint paint = new Paint( );

          我們可以通過下述方法來設置Paint(畫筆)的相關屬性,另外,關于這個屬性有兩種, 圖形繪制相關與文本繪制相關:

          • setARGB(int a,int r,int g,int b): 設置繪制的顏色,a代表透明度,r,g,b代表顏色值。
          • setAlpha(int a): 設置繪制圖形的透明度。
          • setColor(int color): 設置繪制的顏色,使用顏色值來表示,該顏色值包括透明度和RGB顏色。
          • setAntiAlias(boolean aa): 設置是否使用抗鋸齒功能,會消耗較大資源,繪制圖形速度會變慢。
          • setDither(boolean dither): 設定是否使用圖像抖動處理,會使繪制出來的圖片顏色更加平滑和飽滿,圖像更加清晰
          • setFilterBitmap(boolean filter): 如果該項設置為true,則圖像在動畫進行中會濾掉對Bitmap圖像的優化操作, 加快顯示速度,本設置項依賴于dither和xfermode的設置
          • setMaskFilter(MaskFilter maskfilter): 設置MaskFilter,可以用不同的MaskFilter實現濾鏡的效果,如濾化,立體等
          • setColorFilter(ColorFilter colorfilter): 設置顏色過濾器,可以在繪制顏色時實現不用顏色的變換效果
          • setPathEffect(PathEffect effect) 設置繪制路徑的效果,如點畫線等
          • setShader(Shader shader): 設置圖像效果,使用Shader可以繪制出各種漸變效果
          • setShadowLayer(float radius ,float dx,float dy,int color):在圖形下面設置陰影層,產生陰影效果, radius為陰影的角度,dx和dy為陰影在x軸和y軸上的距離,color為陰影的顏色
          • setStyle(Paint.Style style): 設置畫筆的樣式,為FILL,FILL_OR_STROKE,或STROKE
          • setStrokeCap(Paint.Cap cap): 當畫筆樣式為STROKE或FILL_OR_STROKE時,設置筆刷的圖形樣式, 如圓形樣Cap.ROUND,或方形樣式Cap.SQUARE
          • setSrokeJoin(Paint.Join join): 設置繪制時各圖形的結合方式,如平滑效果等
          • setStrokeWidth(float width): 當畫筆樣式為STROKE或FILL_OR_STROKE時,設置筆刷的粗細度
          • setXfermode(Xfermode xfermode): 設置圖形重疊時的處理方式,如合并,取交集或并集,經常用來制作橡皮的擦除效果
          • setFakeBoldText(boolean fakeBoldText): 模擬實現粗體文字,設置在小字體上效果會非常差
          • setSubpixelText(boolean subpixelText): 設置該項為true,將有助于文本在LCD屏幕上的顯示效果
          • setTextAlign(Paint.Align align): 設置繪制文字的對齊方向
          • setTextScaleX(float scaleX): 設置繪制文字x軸的縮放比例,可以實現文字的拉伸的效果
          • setTextSize(float textSize): 設置繪制文字的字號大小
          • setTextSkewX(float skewX): 設置斜體文字,skewX為傾斜弧度
          • setTypeface(Typeface typeface): 設置Typeface對象,即字體風格,包括粗體,斜體以及襯線體,非襯線體等
          • setUnderlineText(boolean underlineText): 設置帶有下劃線的文字效果
          • setStrikeThruText(boolean strikeThruText): 設置帶有刪除線的效果
          • setStrokeJoin(Paint.Join join): 設置結合處的樣子,Miter:結合處為銳角, Round:結合處為圓弧:BEVEL:結合處為直線
          • setStrokeMiter(float miter):設置畫筆傾斜度
          • setStrokeCap (Paint.Cap cap):設置轉彎處的風格 其他常用方法:
          • float ascent( ):測量baseline之上至字符最高處的距離


          • float descent():baseline之下至字符最低處的距離
          • int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth): 檢測一行顯示多少文字
          • clearShadowLayer( ):清除陰影層 其他的自行查閱文檔~

          2)Canvas(畫布):

          畫筆有了,接著就到畫布(Canvas),總不能憑空作畫是吧~常用方法如下:

          首先是構造方法,Canvas的構造方法有兩種:

          Canvas(): 創建一個空的畫布,可以使用setBitmap()方法來設置繪制具體的畫布。

          Canvas(Bitmap bitmap): 以bitmap對象創建一個畫布,將內容都繪制在bitmap上,因此bitmap不得為null。

          接著是 1.drawXXX()方法族:以一定的坐標值在當前畫圖區域畫圖,另外圖層會疊加, 即后面繪畫的圖層會覆蓋前面繪畫的圖層。 比如:

          • drawRect(RectF rect, Paint paint) :繪制區域,參數一為RectF一個區域
          • drawPath(Path path, Paint paint) :繪制一個路徑,參數一為Path路徑對象
          • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) : 貼圖,參數一就是我們常規的Bitmap對象,參數二是源區域(這里是bitmap), 參數三是目標區域(應該在canvas的位置和大小),參數四是Paint畫刷對象, 因為用到了縮放和拉伸的可能,當原始Rect不等于目標Rect時性能將會有大幅損失。
          • drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) : 畫線,參數一起始點的x軸位置,參數二起始點的y軸位置,參數三終點的x軸水平位置, 參數四y軸垂直位置,最后一個參數為Paint 畫刷對象。
          • drawPoint(float x, float y, Paint paint): 畫點,參數一水平x軸,參數二垂直y軸,第三個參數為Paint對象。
          • drawText(String text, float x, floaty, Paint paint) : 渲染文本,Canvas類除了上面的還可以描繪文字,參數一是String類型的文本, 參數二x軸,參數三y軸,參數四是Paint對象。
          • drawOval(RectF oval, Paint paint):畫橢圓,參數一是掃描區域,參數二為paint對象;
          • drawCircle(float cx, float cy, float radius,Paint paint): 繪制圓,參數一是中心點的x軸,參數二是中心點的y軸,參數三是半徑,參數四是paint對象;
          • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint): 畫弧,參數一是RectF對象,一個矩形區域橢圓形的界限用于定義在形狀、大小、電弧,參數二是起始角 (度)在電弧的開始,參數三掃描角(度)開始順時針測量的,參數四是如果這是真的話,包括橢圓中心的電 弧,并關閉它,如果它是假這將是一個弧線,參數五是Paint對象;

          2.clipXXX()方法族:在當前的畫圖區域裁剪(clip)出一個新的畫圖區域,這個畫圖區域就是canvas 對象的當前畫圖區域了。比如:clipRect(new Rect()),那么該矩形區域就是canvas的當前畫圖區域

          3.save()restore()方法: save( ):用來保存Canvas的狀態。save之后,可以調用Canvas的平移、放縮、旋轉、錯切、裁剪等操作! restore():用來恢復Canvas之前保存的狀態。防止save后對Canvas執行的操作對后續的繪制有影響。 save()和restore()要配對使用(restore可以比save少,但不能多),若restore調用次數比save多,會報錯!

          4.translate(float dx, float dy): 平移,將畫布的坐標原點向左右方向移動x,向上下方向移動y.canvas的默認位置是在(0,0)

          5.scale(float sx, float sy):擴大,x為水平方向的放大倍數,y為豎直方向的放大倍數

          6.rotate(float degrees):旋轉,angle指旋轉的角度,順時針旋轉


          3)Path(路徑)

          簡單點說就是描點,連線~在創建好我們的Path路徑后,可以調用Canvas的drawPath(path,paint) 將圖形繪制出來~常用方法如下:

          • addArc(RectF oval, float startAngle, float sweepAngle:為路徑添加一個多邊形
          • addCircle(float x, float y, float radius, Path.Direction dir):給path添加圓圈
          • addOval(RectF oval, Path.Direction dir):添加橢圓形
          • addRect(RectF rect, Path.Direction dir):添加一個區域
          • addRoundRect(RectF rect, float[] radii, Path.Direction dir):添加一個圓角區域
          • isEmpty():判斷路徑是否為空
          • transform(Matrix matrix):應用矩陣變換
          • transform(Matrix matrix, Path dst):應用矩陣變換并將結果放到新的路徑中,即第二個參數。

          更高級的效果可以使用PathEffect類!

          幾個To:

          • moveTo(float x, float y):不會進行繪制,只用于移動移動畫筆
          • lineTo(float x, float y):用于直線繪制,默認從(0,0)開始繪制,用moveTo移動! 比如 mPath.lineTo(300, 300); canvas.drawPath(mPath, mPaint);
          • quadTo(float x1, float y1, float x2, float y2): 用于繪制圓滑曲線,即貝塞爾曲線,同樣可以結合moveTo使用!


          • rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 同樣是用來實現貝塞爾曲線的。 (x1,y1) 為控制點,(x2,y2)為控制點,(x3,y3) 為結束點。 Same as cubicTo, but the coordinates are considered relative to the current point on this contour.就是多一個控制點而已~ 繪制上述的曲線: mPath.moveTo(100, 500); mPath.cubicTo(100, 500, 300, 100, 600, 500); 如果不加上面的那個moveTo的話:則以(0,0)為起點,(100,500)和(300,100)為控制點繪制貝塞爾曲線


          • arcTo(RectF oval, float startAngle, float sweepAngle): 繪制弧線(實際是截取圓或橢圓的一部分)ovalRectF為橢圓的矩形,startAngle 為開始角度, sweepAngle 為結束角度。

          2.動手試試:

          屬性那么多,肯定要手把手的擼一下,才能加深我們的映像是吧~ 嘿嘿,畫圖要么在View上畫,要么在SurfaceView上畫,這里我們在View上畫吧, 我們定義一個View類,然后再onDraw()里完成繪制工作!

          /**

          * Created by Jay on 2015/10/15 0015.

          */

          public class MyView extends View{

          private Paint mPaint;

          public MyView(Context context) {

          super(context);

          init();

          }

          public MyView(Context context, AttributeSet attrs) {

          super(context, attrs);

          init();

          }

          public MyView(Context context, AttributeSet attrs, int defStyleAttr) {

          super(context, attrs, defStyleAttr);

          init();

          }

          private void init(){

          mPaint = new Paint();

          mPaint.setAntiAlias(true); //抗鋸齒

          mPaint.setColor(getResources().getColor(R.color.puple));//畫筆顏色

          mPaint.setStyle(Paint.Style.FILL); //畫筆風格

          mPaint.setTextSize(36); //繪制文字大小,單位px

          mPaint.setStrokeWidth(5); //畫筆粗細

          }

          //重寫該方法,在這里繪圖

          @Override

          protected void onDraw(Canvas canvas) {

          super.onDraw(canvas);

          }

          }

          然后布局那里設置下這個View就好,下述代碼都寫在onDrawable中~


          1)設置畫布顏色:

          canvas.drawColor(getResources().getColor(R.color.yellow)); //設置畫布背景顏色


          2)繪制圓形:

          canvas.drawCircle(200, 200, 100, mPaint); //畫實心圓


          3)繪制矩形:

          canvas.drawRect(0, 0, 200, 100, mPaint); //畫矩形


          4)繪制Bitmap:

          canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 0, 0, mPaint);


          5)繪制弧形區域:

          canvas.drawArc(new RectF(0, 0, 100, 100),0,90,true,mPaint); //繪制弧形區域

          假如true改為false:


          6)繪制圓角矩形

          canvas.drawRoundRect(new RectF(10,10,210,110),15,15,mPaint); //畫圓角矩形


          7)繪制橢圓

          canvas.drawOval(new RectF(0,0,200,300),mPaint); //畫橢圓


          8)繪制多邊形:

          Path path = new Path();

          path.moveTo(10, 10); //移動到 坐標10,10

          path.lineTo(100, 50);

          path.lineTo(200,40);

          path.lineTo(300, 20);

          path.lineTo(200, 10);

          path.lineTo(100, 70);

          path.lineTo(50, 40);

          path.close();

          canvas.drawPath(path,mPaint);


          9)繪制文字:

          canvas.drawText("最喜歡看曹神日狗了~",50,50,mPaint); //繪制文字

          你也可以沿著某條Path來繪制這些文字:

          Path path = new Path();

          path.moveTo(50,50);

          path.lineTo(100, 100);

          path.lineTo(200, 200);

          path.lineTo(300, 300);

          path.close();

          canvas.drawTextOnPath("最喜歡看曹神日狗了~", path, 50, 50, mPaint); //繪制文字


          10)繪制自定義的圖形:

          代碼來源于網上:

          protected void onDraw(Canvas canvas) {

          super.onDraw(canvas);

          canvas.translate(canvas.getWidth()/2, 200); //將位置移動畫紙的坐標點:150,150

          canvas.drawCircle(0, 0, 100, mPaint); //畫圓圈

          //使用path繪制路徑文字

          canvas.save();

          canvas.translate(-75, -75);

          Path path = new Path();

          path.addArc(new RectF(0,0,150,150), -180, 180);

          Paint citePaint = new Paint(mPaint);

          citePaint.setTextSize(14);

          citePaint.setStrokeWidth(1);

          canvas.drawTextOnPath("繪制表盤~", path, 28, 0, citePaint);

          canvas.restore();

          Paint tmpPaint = new Paint(mPaint); //小刻度畫筆對象

          tmpPaint.setStrokeWidth(1);

          float y=100;

          int count = 60; //總刻度數

          for(int i=0 ; i <count ; i++){

          if(i%5 == 0){

          canvas.drawLine(0f, y, 0, y+12f, mPaint);

          canvas.drawText(String.valueOf(i/5+1), -4f, y+25f, tmpPaint);

          }else{

          canvas.drawLine(0f, y, 0f, y +5f, tmpPaint);

          }

          canvas.rotate(360/count,0f,0f); //旋轉畫紙

          }

          //繪制指針

          tmpPaint.setColor(Color.GRAY);

          tmpPaint.setStrokeWidth(4);

          canvas.drawCircle(0, 0, 7, tmpPaint);

          tmpPaint.setStyle(Paint.Style.FILL);

          tmpPaint.setColor(Color.YELLOW);

          canvas.drawCircle(0, 0, 5, tmpPaint);

          canvas.drawLine(0, 10, 0, -65, mPaint);

          }


          本節小結:

          本節我們對android.graphics接口類下的三個繪圖API:Canvas(畫布),Paint(畫筆),Path(路徑)進行 了學習,方法有很多,別去死記,用到的時候查就好,這里我們先有個大概映像即可,自定義控件那里 我們再來慢慢糾結~好的,就說這么多


          主站蜘蛛池模板: 色偷偷av一区二区三区| 一区二区三区日本电影| 中文字幕一区二区在线播放| 精品一区二区三区无码免费视频| 欧洲精品码一区二区三区免费看 | 影院成人区精品一区二区婷婷丽春院影视 | 影音先锋中文无码一区| 蜜桃传媒视频麻豆第一区| 国产一区内射最近更新| 成人精品一区二区三区校园激情| 亚洲av成人一区二区三区| 精品国产一区二区三区免费| 在线观看国产区亚洲一区成人| 亚洲国产成人久久一区WWW | 福利电影一区二区| 中文字幕在线不卡一区二区| 一区二区三区在线免费看| 国产午夜福利精品一区二区三区 | 伊人色综合一区二区三区影院视频 | 久久亚洲国产精品一区二区| 日本道免费精品一区二区| 亚洲一区二区视频在线观看 | 美女视频一区三区网站在线观看| 美女啪啪一区二区三区| 亚洲图片一区二区| 日本内射精品一区二区视频| 在线中文字幕一区| 精品欧洲av无码一区二区| 97精品国产一区二区三区| 蜜臀AV无码一区二区三区| 多人伦精品一区二区三区视频| 麻豆国产在线不卡一区二区| 国偷自产视频一区二区久| 天码av无码一区二区三区四区 | 亚洲AV网一区二区三区| 日韩AV无码一区二区三区不卡 | 国产精品一区二区三区久久| 国产主播一区二区三区| 亚洲一区日韩高清中文字幕亚洲| 成人精品一区二区三区中文字幕| 国产爆乳无码一区二区麻豆 |