1. 程式人生 > >自定義控制元件玩套路以及canvas StaticLayout的使用

自定義控制元件玩套路以及canvas StaticLayout的使用

遇到自定義控制元件的時候很苦惱、不知道從哪裡下手、鄙人也是新手、記錄下開發的思路
我們有時候需要把某一個功能封裝成控制元件、很簡單、寫好佈局、將其inflate出來
必要的屬性、就跟普通的activity一樣定義即可、context直接呼叫getContext();

public class DanmakuChannel extends RelativeLayout {

    public boolean isRunning = false;
    public DanmakuEntity mEntity;
    private DanmakuActionInter danAction;

    public
DanmakuActionInter getDanAction() { return danAction; } public void setDanAction(DanmakuActionInter danAction) { this.danAction = danAction; } public DanmakuChannel(Context context) { super(context); init(); } public DanmakuChannel(Context context, AttributeSet attrs) { super
(context, attrs); init(); } public DanmakuChannel(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public DanmakuChannel(Context context, AttributeSet attrs, int
defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { LayoutInflater inflater = LayoutInflater.from(getContext()); inflater.inflate(R.layout.danmaku_channel_layout, null); } }

定義完了、在佈局中呼叫即可

<com.just.sun.widget.danmu.DanmuBase.DanmakuChannel
            android:id="@+id/danA"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginTop="10dp" />

要處理點選事件、很簡單、可以findViewById setOnclickListener、跟Activity是一樣的、
如果更麻煩一點要捕獲觸控事件、需要重寫方法
下面我通過點選做了個小動畫、點選螢幕會出現晃動的小手、然後消失

    public View.OnTouchListener onLightTouchListener = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (model.liveEntity != null) {
                float x = event.getX();
                float y = event.getY();
                final int action = MotionEventCompat.getActionMasked(event);
                switch (action) {
                    case MotionEvent.ACTION_DOWN://單點觸控動作
                        break;
                    case MotionEvent.ACTION_UP://單點觸控離開動作

                        ImageView imageView = new ImageView(binding.getRoot().getContext());
                        imageView.setImageResource(R.drawable.light_touch);
                        final int width = imageView.getWidth();
                        final int height = imageView.getHeight();
                        //如果在邊緣就不繪製
                        if (binding.getRoot().getWidth() - x > 130
                                && binding.getRoot().getHeight() - y > 130) {
                            sengLight();
                            RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                            imageView.setLayoutParams(param);
                            param.setMargins((int) x - 130 / 2, (int) y - 130 / 2, 0, 0);
                            binding.zanContainer.addView(imageView);
                            startAnim(x, y, imageView);
                        }
                        break;
                    case MotionEvent.ACTION_MOVE://觸控點移動動作
                        break;
                    case MotionEvent.ACTION_CANCEL://觸控動作取消
                        break;
                    case MotionEvent.ACTION_POINTER_DOWN://多點觸控動作
                        break;
                    case MotionEvent.ACTION_POINTER_UP://多點離開動作
                        break;
                }
                return true;
            }
            return true;
        }
    };

通過上面的case大家看到了、我獲取控制元件的高寬是通過 getWidth()
但是有時候、獲取到是0、是因為控制元件還沒有繪製完成、那麼我們可以

view.measure(-1, -1);
int measuredWidth = view.getMeasuredWidth();

如果我們自定義控制元件、需要canvas畫的時候、獲取到文字或者是bitmap的高寬很重要、
因為通過自定義座標來定義畫的起始位置、預設位置是 0,0 也就是左上角

畫文字 我麼常用到StaticLayout、下面講一下StaticLayout
TextView就是用StaticLayout畫的
他有三個建構函式

public StaticLayout(CharSequence source,
                    TextPaint paint,
                    int width,
                    Layout.Alignment align,
                    float spacingmult,
                    float spacingadd,
                    boolean includepad)

public StaticLayout(CharSequence source,
                    int bufstart,
                    int bufend,
                    TextPaint paint,
                    int outerwidth,
                    Layout.Alignment align,
                    float spacingmult,
                    float spacingadd,
                    boolean includepad)

public StaticLayout(CharSequence source,
                    int bufstart,
                    int bufend,
                    TextPaint paint,
                    int outerwidth,
                    Layout.Alignment align,
                    float spacingmult,
                    float spacingadd,
                    boolean includepad,
                    TextUtils.TruncateAt ellipsize,
                    int ellipsizedWidth)

最後一個構造引數講解一下

1.需要分行的字串

2.需要分行的字串從第幾的位置開始

3.需要分行的字串到哪裡結束

4.畫筆物件

5.layout的寬度,字串超出寬度時自動換行。

6.layout的對其方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三種。

7.相對行間距,相對字型大小,1.5f表示行間距為1.5倍的字型高度。

8.在基礎行距上新增多少

實際行間距等於這兩者的和。

9.引數未知

10.從什麼位置開始省略

11.超過多少開始省略
通常是這麼用的

StaticLayout staticLayout = new StaticLayout(text, paint, (int) Math.ceil(StaticLayout.getDesiredWidth(text, paint)), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
                width = staticLayout.getWidth();
                height = staticLayout.getHeight();

getDesiredWidth(text, paint) 是Layout的方法、取得字串顯示的寬度。

也可以這樣獲取寬度

TextPaint paint = new TextPaint();
        paint.setTextSize(textSize);
        float contentLength = paint.measureText(content, 0, content.length());
        return contentLength;
        //這個方法 如果使用了ImageSpan會失效、建議用上一種方法

下面搞一下 canvas、站在巨人的肩膀上

一個畫圖需要四大基本要素:
1、一個用來儲存畫素的Bitmap;
2、一個Canvas畫布,繪製Bitmap操作;
3、繪製的東西
4、繪製的畫筆Paint(顏色和樣式)

1、如何獲得一個Canvas物件。

Canvas物件的獲取方式有三種:

第一種我們通過重寫View.onDraw方法,View中的Canvas物件會被當做引數傳遞過來,我們操作這個Canvas,效果會直接反應在View中。

第二種就是當你想自己建立一個Canvas物件。從上面的基本要素可以明白,一個Canvas物件一定是結合了一個Bitmap物件的。所以一定要為一個Canvas物件設定一個Bitmap物件。

 //得到一個Bitmap物件,當然也可以使用別的方式得到。但是要注意,改bitmap一定要是mutable(異變的)  
        Bitmap b = Bitmap.createBitmap(100,100, Bitmap.Config.ARGB_8888);  
        Canvas c = new Canvas(b);  
        /**先new一個Canvas物件,在呼叫setBitmap方法,一樣的效果 
         * Canvas c = new Canvas(); 
         * c.setBitmap(b); 
         */ 

第三種方式,是呼叫SurfaceHolder.lockCanvas(),返回一個Canvas物件。
2、canvas可以繪製的內容

1)填充

  drawARGB(int a, int r, int g, int b)
  drawColor(int color)
  drawRGB(int r, int g, int b)
  drawColor(int color, PorterDuff.Mode mode)

2)幾何圖形

canvas.drawArc (扇形)

canvas.drawCircle(圓)

 canvas.drawOval(橢圓)

 canvas.drawLine(線)

 canvas.drawPoint(點)

 canvas.drawRect(矩形)

 canvas.drawRoundRect(圓角矩形)

 canvas.drawVertices(頂點)

 cnavas.drawPath(路徑)

3)圖片

   canvas.drawBitmap (點陣圖)

   canvas.drawPicture (圖片)

4)文字

   canvas.drawText

3. Canvas的儲存和回滾

為了方便一些轉換操作,Canvas還提供了儲存和回滾屬性的方法(save和restore),比如你可以先儲存目前畫紙的位置(save),然後旋轉90度,向下移動100畫素後畫一些圖形,畫完後呼叫restore方法返回到剛才儲存的位置。

Canvas提供的該功能的API如下:


    /** 
         * 儲存當前的matrix和clip到私有的棧中(Skia內部實現)。任何matrix變換和clip操作都會在呼叫restore的時候還原。 
         * @return 返回值可以傳入到restoreToCount()方法,以返回到某個save狀態之前。 
         */  
        public native int save();  



        /** 
         * 傳入一個標誌,來表示當restore 的時候,哪些引數需要還原。該引數定義在Canvas中,參照下面。 
         * save()方法預設的是還原matrix和clip,但是可以使用這個方法指定哪些需要還原。並且只有指定matrix和clip才有效,其餘的幾個引數是 
         * 用於saveLayer()和saveLayerAlpha()方法 的。 
         */  
        public native int save(int saveFlags);  


        /** 
         * 回到上一個save呼叫之前的狀態,如果restore呼叫的次數大於save方法,會出錯。 
         */  
        public native void restore();  

         /** 
         * 返回棧中儲存的狀態,值等譯 save()呼叫次數-restore()呼叫次數 
         */  
        public native int getSaveCount();  




        /** 
         * 回到任何一個save()方法呼叫之前的狀態 
         */  
        public native void restoreToCount(int saveCount);  



    /**saveFlags的引數*/  
     public static final int MATRIX_SAVE_FLAG = 0x01;//需要還原Matrix  
        public static final int CLIP_SAVE_FLAG = 0x02;//需要還原Clip  
    /**下面三個引數在saveLayer的時候使用,具體作用,沒有搞明白*/  
       public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04;  
      public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08;  
      public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;  
        public static final int ALL_SAVE_FLAG = 0x1F; //還原所有  

    /*關於saveLayer的具體flags還不大明白它的含義,具體怎麼使用在下面例子中*/  
     public int saveLayer(RectF bounds, Paint paint, int saveFlags)  
    public int saveLayer(float left, float top, float right, float bottom,  
                             Paint paint, int saveFlags)   
     public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)  
    public int saveLayerAlpha(float left, float top, float right, float bottom,  
                                  int alpha, int saveFlags)  

這裡寫圖片描述

saveLayer

Canvas 在一般的情況下可以看作是一張畫布,所有的繪圖操作如drawBitmap, drawCircle都發生在這張畫布上,這張畫板還定義了一些屬性比如Matrix,顏色等等。但是如果需要實現一些相對複雜的繪圖操作,比如多層動畫,地圖(地圖可以有多個地圖層疊加而成,比如:政區層,道路層,興趣點層)。Canvas提供了圖層(Layer)支援,預設情況可以看作是隻有一個圖層Layer。如果需要按層次來繪圖,Android的Canvas可以使用SaveLayerXXX, Restore 來建立一些中間層,對於這些Layer是按照“棧結構“來管理的:
這裡寫圖片描述

建立一個新的Layer到“棧”中,可以使用saveLayer, savaLayerAlpha, 從“棧”中推出一個Layer,可以使用restore,restoreToCount。但Layer入棧時,後續的DrawXXX操作都發生在這個Layer上,而Layer退棧時,就會把本層繪製的影象“繪製”到上層或是Canvas上,在複製Layer到Canvas上時,可以指定Layer的透明度(Layer),這是在建立Layer時指定的:public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)本例Layers 介紹了圖層的基本用法:Canvas可以看做是由兩個圖層(Layer)構成的,為了更好的說明問題,我們將程式碼稍微修改一下,預設圖層繪製一個紅色的圓,在新的圖層畫一個藍色的圓,新圖層的透明度為0×88。

 public class Layers extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(new SampleView(this));  
    }  

    private static class SampleView extends View {  
        private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG  
                | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG  
                | Canvas.CLIP_TO_LAYER_SAVE_FLAG;  

        private Paint mPaint;  

        public SampleView(Context context) {  
            super(context);  
            setFocusable(true);  

            mPaint = new Paint();  
            mPaint.setAntiAlias(true);  
        }  

        @Override  
        protected void onDraw(Canvas canvas) {  
            canvas.drawColor(Color.WHITE);    
            canvas.translate(10, 10);    
            mPaint.setColor(Color.RED);    
            canvas.drawCircle(75, 75, 75, mPaint);    
            canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, LAYER_FLAGS);    
            mPaint.setColor(Color.BLUE);    
            canvas.drawCircle(125, 125, 75, mPaint);    
            canvas.restore();   
         }  
    }  
}