自定义View--CascadeView
来源:互联网 发布:核聚变 不能实现 知乎 编辑:程序博客网 时间:2024/06/11 13:27
为什么要自定义View?
- 自定义View可以大大简化布局层次,提高效率
- 原生控件无法满足需求的时候,自定义View就会显得非常重要
- 程序员掌握了非常大的自由,只要遵循一定的步骤,几乎可以完成所有你能想到的控件,当然这个过程还是有很多细节和需要注意的地方的。
自定义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; }
- 接下来最重要的异步就是重写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(); }
- 在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); }
- 重写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; }
- 最后一点就是在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提示!
- 自定义View--CascadeView
- 自定义view
- 自定义View
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义View
- 自定义view
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义View
- 在线工具
- java String 常用方法
- nodejs IDE 编译器
- 28. Implement strStr() leetcode Python new season 2016
- JAVA相对路径、绝对路径
- 自定义View--CascadeView
- iOS中深拷贝和浅拷贝的3种理解和1个注意点
- UML箭头讲解
- linux php安装扩展
- VS2010与Win7共舞:UAC与数据重定向
- scrollview 滚动到最顶端
- iOS动态计算Label的宽高
- ArcGIS教程:地形转栅格的工作原理(二)
- Linux文件的三个时间详解