自定义View--CascadeView

来源:互联网 发布:核聚变 不能实现 知乎 编辑:程序博客网 时间:2024/06/11 13:27

为什么要自定义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提示!

0 0