自定義View--CascadeView

爲什麼要自定義View?

  1. 自定義View可以大大簡化佈局層次,提高效率
  2. 原生控件無法滿足需求的時候,自定義View就會顯得非常重要
  3. 程序員掌握了非常大的自由,只要遵循一定的步驟,幾乎可以完成所有你能想到的控件,當然這個過程還是有很多細節和需要注意的地方的。

自定義View的步驟
這裏我們做一個和前文
自定義ViewGroup–CascadeLayout類似的控件CascadeView,還是先看一下效果圖
這裏寫圖片描述
1. 繼承View,構造函數

CascadeView extends View

2.然後在構造函數中進行一些初始化的操作

在使用構造函數的時候有一點需要注意的地方,如果是使用Java代碼創建CascadeView,一般我們會使用CascadeView(Context context),如果相應在XML文件中使用CascadeView,我們需要使用CascadeView(Context context, AttributeSet attrs)這個構造函數,否則不會起作用的。爲了防止代碼的冗餘,可以把一些相同的代碼抽取出來,這裏的示例我就不這麼做了。

    private Bitmap[] mPokers = new Bitmap[3];
    private int[] mPokersId = new int[] {R.drawable.poker_39,R.drawable.poker_40,R.drawable.poker_48};

    private int mHeight;
    private int mWidth;
    private int mPaddingTop;
    private int mPaddingLeft;
 public CascadeView(Context context) {
        super(context);
    }

    public CascadeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeView);
        mHeight = a.getDimensionPixelSize(R.styleable.CascadeView_poker_height, 0);
        mWidth = a.getDimensionPixelSize(R.styleable.CascadeView_poker_width, 0);
        mPaddingTop = a.getDimensionPixelSize(R.styleable.CascadeView_poker_paddingTop, 0);
        mPaddingLeft = a.getDimensionPixelSize(R.styleable.CascadeView_poker_paddingLeft, 0);

        for(int i=0;i<mPokers.length;i++){
            mPokers[i] = drawableToBitmap(mPokersId[i],mWidth,mHeight);
        }
    }

這裏有一個將drawable資源id轉換爲bitmap的函數

    private Bitmap drawableToBitmap(int drawableId,int width,int height)
    {    
        return  Bitmap.createScaledBitmap(
                BitmapFactory.decodeResource(getResources(), drawableId), width, height, false);
    }

如果是drawable轉換爲Bitmap,可以使用下面的函數

    private Bitmap drawableToBitmap(Drawable drawable)
    {
        if(drawable instanceof BitmapDrawable){
            BitmapDrawable bd = (BitmapDrawable) drawable;
            return bd.getBitmap();
        }

        int h = drawable.getIntrinsicHeight();
        int w = drawable.getIntrinsicWidth();
        Bitmap  bitmap = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0,0,w,h);
        drawable.draw(canvas);
        return bitmap;
    }
  1. 接下來最重要的異步就是重寫onDraw函數了,ondraw函數就是把東西繪製到你的屏幕上,這個函數在界面刷新的時候會頻繁的刷新(例如我們在代碼中調用invalidate,都會調用onDraw函數),因此,有一個非常重要的原則就是,不能再onDraw方法中進行復雜耗時的操作,如果非要做的話,可以放到異步線程裏面去,不要再UI線程中。
@Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);

        canvas.save();
        for(Bitmap b:mPokers){
            canvas.translate(mPaddingLeft, mPaddingTop);
            canvas.drawBitmap(b, 0, 0,null);
        }
        canvas.restore();
    }
  1. 在XML文件中使用
     <com.daven.demo.CascadeView
          android:id="@+id/cascadeview"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginTop="20dp"
          daven:poker_height="130dp"
          daven:poker_paddingTop="20dp"
          daven:poker_paddingLeft="30dp"
          daven:poker_width="100dp" />

好了,到了這裏,程序其實就已經差不多了。

使用ClipRect優化過度繪製

但是有一點,其實我們完全可以對程序進行一個優化。我們把手機的GPU過度繪製打開,可以看到下面的圖:
這裏寫圖片描述
其中綠色和紅色的部分,就是幾張撲克的重疊的部分,我們完全可以使用ClipRect把這幾個部分的東西剪裁掉,只保留最上面的一層。整個過程如下

    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);

        canvas.save();
        for(int i = 0; i < mPokers.length; i++){
            canvas.translate(mPaddingLeft, mPaddingTop);
            canvas.save();
            if( i < mPokers.length -1){
                canvas.clipRect(0,0,mWidth,mHeight);
                canvas.clipRect( mPaddingLeft, mPaddingTop, mWidth, mHeight,Region.Op.DIFFERENCE);
            } 
            canvas.drawBitmap(mPokers[i], 0, 0, null);
            canvas.restore();
        }
        canvas.restore();
    }

這裏我們需要說明一下Region.Op的用法和區別

  • DIFFERENCE:之前剪切過除去當前要剪切的區域;
  • INTERSECT:當前要剪切的區域在之前剪切過內部的部分;
  • UNION:當前要剪切的區域加上之前剪切過內部的部分;
  • XOR:異或,當前要剪切的區域與之前剪切過的進行異或;
  • REVERSE_DIFFERENCE:與DIFFERENCE相反,以當前要剪切的區域爲參照物,當前要剪切的區域除去之前剪切過的區域;
  • REPLACE:用當前要剪切的區域代替之前剪切過的區域。
  • 沒帶Op參數效果與INTERSECT的效果一樣,兩個區域的交集。
    我們可以看到那張K,被我們剪裁成這個樣子了,重疊的部分完全沒有了
    這裏寫圖片描述

處理View上面的點擊事件onTouchEvent

到了這裏,我們的View放在那裏好像還只是一個擺設,我們想如果能讓其相應我們的點擊事件那該多好,當時view本身就是可以有監聽函數的,但是如果我們需要在點擊該view的某個區域進行相應該怎麼辦呢?
這個時候我們就需要自己寫監聽函數了!
1.首先我們寫一個監聽器的接口

    private OnViewClickListener mOnClickListener;

    public void setOnClickListener(OnViewClickListener e) {
        mOnClickListener = e;
    }

    public interface OnViewClickListener {
        public void OnClick(int position);
    }
  1. 重寫onTouchEvent(), 判斷點擊的座標是否在K那張牌上面,如果是就響應這個回調函數
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch(event.getAction()){    
        case MotionEvent.ACTION_UP:
            if( (x > mPaddingLeft && x < 2 * mPaddingLeft 
                    && y > mPaddingTop && y < mHeight)||
                    (x > mPaddingLeft && x < mWidth
                            && y > mPaddingTop && y < 2*mPaddingTop) ){
                mOnClickListener.OnClick(0);
            }
            return true;
        }
        return true;
    }
  1. 最後一點就是在MainActivity中使用這個監聽器了,和我們使用其他的監聽器一樣!
        CascadeView c = (CascadeView) findViewById(R.id.cascadeview);
        c.setOnClickListener(new CascadeView.OnViewClickListener() {

            @Override
            public void OnClick(int position) {
                Toast.makeText(getBaseContext(), " You clicked K", Toast.LENGTH_SHORT).show();
            }
        });

點擊到了之後就會回調到這個函數,然後彈出Toast提示!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章