澳门太阳娱乐集团官网-太阳集团太阳娱乐登录

Android 自定义View学习——PorterDuffXfermode简单练习
分类:脚本专栏

最简单基础的刮刮乐

学习资料:

学习资料:

图片 1简易效果的刮刮乐

  • Android群英传
  • Android开发艺术探索
  • 爱哥自定义控件其实很简单3/4
public class XferModeView extends View { private Paint mPaint; private Bitmap dstBitmap, srcBitmap; private Canvas mCanvas; private Path mPath; public XferModeView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setAlpha; mPaint.setStrokeWidth; mPaint.setStyle(Paint.Style.STROKE); dstBitmap = decodeBitmapFormRes(getResources(), R.drawable.text, getWidth; srcBitmap = Bitmap.createBitmap(dstBitmap.getWidth(), dstBitmap.getHeight(), Bitmap.Config.ARGB_8888);//这里不能RGB565 mCanvas = new Canvas(srcBitmap); mCanvas.drawColor(Color.GRAY); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw; canvas.drawBitmap(dstBitmap, 0, 0, null); drawPath(); canvas.drawBitmap(srcBitmap, 0, 0, null); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction { case MotionEvent.ACTION_DOWN: mPath.reset();//路径复位空白 mPath.moveTo(event.getX(), event.getY; break; case MotionEvent.ACTION_MOVE: mPath.lineTo(event.getX(), event.getY; break; } invalidate(); return true; } private void drawPath() { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mCanvas.drawPath(mPath, mPaint); } /** * 测量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension; } else if (wSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(wSpecSize, 300); } } /** * 图片的缩放 */ private Bitmap decodeBitmapFormRes(Resources resources, int resId, int targetWidth, int targetHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; options.inJustDecodeBounds = false; BitmapFactory.decodeResource(resources, resId, options); int inSample = calculateInSample(options, targetWidth, targetHeight); options.inSampleSize = inSample; return BitmapFactory.decodeResource(resources, resId, options); } private int calculateInSample(BitmapFactory.Options options, int targetWidth, int targetHeight) { if (targetWidth <= 0 || targetHeight <= 0) { return 1; } int inSample = 1; final int rawWidth = options.outWidth; final int rawHeight = options.outHeight; if (rawWidth > targetWidth || rawHeight > targetHeight) { final int halfWidth = rawWidth / 2; final int halfHeight = rawHeight / 2; while ((halfWidth / inSample >= targetWidth) && (halfHeight / inSample >= targetHeight)) { inSample *= 2; } } return inSample; }}

PorterDuffXfermode有点类似数学中的交集,并集,用来两个图像间的混合显示模式,设置的是两个图层交集区域的显示方式,dst是下层,先画的图形;src是上层,后画的图形

一般自定义一个View有4种思路:

待解决问题:

构造方法:

  1. 直接继承View
  2. 直接继承ViewGroup
  3. 继承现有的View,例如ImageViweSurfaceView
  4. 继承现有的ViewGroup,例如LinearLayout
  1. 图片大时,滑动画笔会卡
  2. 图片小时,不能全屏,屏幕边缘有空白
  • PorterDuffXfermode(PorterDuff.Mode mode)

推荐优先考虑3或者4。原因也比较明显,无论哪一种方式,需要注意的事项都不少。采用3或者4的话,现有控件的一些特性可以直接拿来用,而且也更加容易做适配

两个问题,暂时想到解决思路:

构造方法中只需一个参数,PorterDuff.ModeModePorterDuff这个类中的一个枚举类,现在Mode中有18个枚举值,图中只有16个,图少了ADDOVERLAY

之前有一个误区,总觉得自定义View就得是直接继承View,然后自己实现各种效果

  1. 继承SurfaceView,Android SurfaceView入门学习2.使用Matrix.setRectToRect()方法,View的测量方法onMeasure()学习有实际案例

图片 2PorterDuff.Mode错误图

在之前的学习中,重写onMeasure()方法的目的就是为了支持wrap_content。原因在学习ViewViewGroup中,也进行了尝试说明,View是的大小是要受自身和父容器ViewGroup影响。想要让wrap_content拥有包裹内容的特性,就需要重写ViewViewGrouponMeausre()方法,针对要加载内容或者childView的宽高来设置自定义View的宽高。这也是为啥推荐方式3或者4的原因之一

上面的图是有错误的,下面的图,和我自己测试的结果是一致的。错误原因可以去Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解看看

  1. 直接继承的ViewMargin是不用考虑的,之前的学习提到过,这个属性被封装进了LayoutParamas由父容器ViewGroup来处理。Padding属性需要根据需求在onMeasure()onDraw()方法中处理
  2. 直接继承的ViewGroup,在onMeadsure()方法中,ViewGroup自身的Margin属性是直接支持的。需要考虑的是封装进了LayoutParamas中的childViewMargin属性信息。利用childView拿到的LayoutParams中的Margin属性信息,在测量时就对childView的宽高进行处理。在onLayout()确定childView四个顶点位置时,将Margin属性信息也做处理。Padding属性的处理类似Margin,也需要在onMeausreonLayout()方法中,都进行处理

图片 3修正后的PorterDuff.Mode

View自身提供了post系列方法,里面封装的有Handler。除非需要明确使用Handlder来发送消息

最常用的就是DST_INSRC_IN,是可以用来实现自定义CircleImageView的一种方式

注意:post(new Runnable中的一系列操作依然是在主线程,最好不要进行耗时的操作。而执行网络请求会直接造成应用崩溃,给出android.os.NetworkOnMainThreadException异常

看图帮助:后来者居上

在Android 自定义View学习——Bezier贝塞尔曲线学习中,处理过一次这样的情况

  • 黄色的圆是DST下层,先进行绘制;
  • 蓝色的矩形是SRC上层,后进行绘制

主要使用到一个onDetachedFromWindow()的方法。在View所在的Activity退出或者当前View自身被remove点时,ViewonDetachedFromWindow()方法便会被回调。在这个方法内,将正在运行的动画或者线程关闭掉,能够减少内存泄露的出现

模式 效果
PorterDuff.Mode.CLEAR 上层绘制不会提交到画布,并把与下层交集部分也清除
PorterDuff.Mode.SRC 显示上层绘制,此时下层绘制也会显示
PorterDuff.Mode.DST 显示下层绘制,而上层不会绘制
PorterDuff.Mode.SRC_OVER 正常显示,上层叠盖在下层之上
PorterDuff.Mode.DST_OVER 上下层都显示,下层在上
PorterDuff.Mode.SRC_IN 将交集显示在下层绘制的区域
PorterDuff.Mode.DST_IN 显示下层绘制
PorterDuff.Mode.SRC_OUT 取非交集区域
PorterDuff.Mode.DST_OUT 取下层非交集区域
PorterDuff.Mode.SRC_ATOP 取上层的交集区域和下层的非交集区域
PorterDuff.Mode.DST_ATOP 下层在上层之上
PorterDuff.Mode.XOR 去除两层的交集区域
PorterDuff.Mode.DARKEN 取两层全部区域,交集区域变暗
PorterDuff.Mode.LIGHTEN 取两层全部区域,交集区域变亮
PorterDuff.Mode.MULTIPLY 取下层全部区域,交集区域色彩叠加,正片叠底
PorterDuff.Mode.SCREEN 取两层全部区域,交集区域变透明
PorterDuff.Mode.ADD 饱和度叠加
PorterDuff.Mode.OVERLAY 对黑白无效,显示两层颜色中和后的中间色

继承之一些可以滑动的ViewGroup后,常需要考虑滑动冲突。View的事件体系,下篇开始学习。

PorterDuff.Mode不单单是进行了图形的操作,有的模式还对色彩有影响

以上都是看Android开发艺术探索后做的总结

这些模式最好都自己测试一下

虽然之前学习过程中,用过了十几次了,但每次都是按照固定套路来,并没有进行一些思考。

具体的过程可以学习爱哥的自定义控件其实很简单1/6

  1. 构造方法又都有啥啥区别?
  2. View有没有生命周期? 有
  3. ViewActivity的生命周期有啥联系没有? 有

有的模式,不支持硬件加速,最好关闭硬件加速

这里之前搞错了。说ViewActivity的生命周期没有联系。View依赖于Activity,不可能没有联系。感谢,Android之路留言指出错误。但,具体啥联系,暂时不打算学习,想留在后面具体学习Activity时,再来补充

绘制过程后来者居上,dst为下层,先进行绘制;src为上层,后进行绘制。

直接继承View后,代码:

生活中的例子: 鸡蛋灌饼 : )

public class LifecycleView extends View { private final String TAG = "LifecycleView"; public LifecycleView(Context context) { super; Log.e(TAG,"&&&第1个构造方法"); } public LifecycleView(Context context, AttributeSet attrs) { super(context, attrs); Log.e(TAG,"&&&第2个构造方法"); for(int i=0;i<attrs.getAttributeCount{ Log.e(TAG, "&&&第2个构造方法"+attrs.getAttributeName+"--->"+attrs.getAttributeValue; } } public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG,"&&&第3个构造方法"); } public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); Log.e(TAG,"&&&第4个构造方法"); }}

简单测试:

打印出的Log信息:

图片 4PorterDuff.Mode.MULTIPLY

图片 5Log信息

public class PorterDuffView extends View { private Paint paint ; private RectF rectF; private PorterDuffXfermode porterDuffXfermode; public PorterDuffView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); } private void initPaint() { setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速 paint = new Paint(Paint.ANTI_ALIAS_FLAG); rectF = new RectF(150,150,500,500); porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY); } @Override protected void onDraw(Canvas canvas) { super.onDraw; //绘制背景色 canvas.drawColor(Color.YELLOW); //创建一个新的画布Layer int layerId = canvas.saveLayer(0, 0, getWidth(),getHeight() , null, Canvas.ALL_SAVE_FLAG); //绘制dst层 float x = getWidth() / 4; float y = getHeight() / 4; float radius = Math.min(getWidth(), getHeight / 4; paint.setColor(Color.CYAN); canvas.drawCircle(x, y, radius, paint); //设置混合模式 paint.setColor(Color.RED); paint.setXfermode(porterDuffXfermode); //绘制src层 canvas.drawRect(rectF,paint); paint.setXfermode; canvas.restoreToCount;//将自己创建的画布Layer绘制到画布默认的Layer } /** * 测量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension; } else if (wSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(wSpecSize, 300); } }}

根据Log信息,很明显得出,当在xml布局文件中声明了LifecycleView,系统就会调用第2个构造方法,并且通过参数attrs可以拿到布局中,LifecycleView中的标签属性。布局中控件的属性就封装在AttributeSet

之所以更改图层是为了让绘制不受背景色影响。将绘制的圆形和矩形在一个新的图层绘制,绘制完成后,再把新的图层加入到Canvas的默认图层。

第1个构造方法是直接进行new LifecycleView时,会被调用,但很少这样用

如果只是单单为了显示一个圆形的图片,可以有好多种办法。

结论1:当在布局中使用自定义View时,调用第2个构造方法

可以借助PorterDuffXfermode或者v4包下的RoundedBitmapDrawable进行把Biamp进行形状上的改变得到圆形

可第3和第4构造方干嘛用的?

若不是经常大量的频繁的显示出一个圆形图片,也可以利用CardView,Android 一个另类的显示圆形图片方式

第3个构造方法,需要一个declare-styleable自定义属性

本篇为了学习PorterDuffXfermode就使用前面提到的PorterDuff.Mode.SRC_IN

关于自定义属性可以看看鸿洋大神的Android 深入理解Android中的自定义属性

简单实践:

先在style.xnl布局文件中声明一个

图片 6圆形图片

 <declare-styleable name="CustomProperty"> <attr name="number" format="integer" /> <attr name="text" format="string" /></declare-styleable>
public class CircleImageView extends ImageView { private Paint mPaint; public CircleImageView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } @Override protected void onDraw(Canvas canvas) { BitmapDrawable drawable = (BitmapDrawable) getDrawable(); if (drawable != null) { Bitmap bitmap = drawable.getBitmap(); drawTargetBitmap(canvas, bitmap); } } private void drawTargetBitmap(Canvas canvas, Bitmap bitmap) { final int sc = canvas.saveLayer(0, 0, getWidth(),getHeight(), null, Canvas.ALL_SAVE_FLAG); //先绘制dst层 final float x = getWidth() / 2; final float y = getHeight() / 2; final float radius = Math.min(getWidth(), getHeight / 2; canvas.drawCircle(x, y, radius, mPaint); //设置混合模式 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //绘制src层 final float f_x = getWidth() / 2 -bitmap.getWidth() / 2; final float f_y = getHeight() / 2 -bitmap.getHeight() / 2; canvas.drawBitmap(bitmap, f_x,f_y, mPaint); // 还原混合模式 mPaint.setXfermode; // 还原画布 canvas.restoreToCount; }}

定义好自定义属性后,LifecycleView中使用:

一个超级简单的自定义CircleImageView

<RelativeLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" xmlns:custom="http://schemas.android.com/apk/res-auto"> <com.szlk.customview.custom.LifecycleView custom:text="英勇青铜5" custom:number="521" android:layout_width="100dp" android:layout_height="100dp" /></RelativeLayout>

简单介绍完毕,主要就是18种模式的理解,最好每种模式的效果都能够明白,掌握最常用的DST_INSRC_IN

修改LifecycleView代码:

进一步学习,可以看鸿洋大神的Android 自定义控件实现刮刮卡效果 真的就只是刮刮卡么

public class LifecycleView extends View { private final String TAG = "LifecycleView"; public LifecycleView(Context context) { super; Log.e(TAG,"&&&第1个构造方法"); } public LifecycleView(Context context, AttributeSet attrs) { super(context, attrs); Log.e(TAG,"&&&第2个构造方法"); for(int i=0;i<attrs.getAttributeCount{ Log.e(TAG, "&&&&&&第2个构造方法"+attrs.getAttributeName+"--->"+attrs.getAttributeValue; } } public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG,"&&&第3个构造方法"); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomProperty); String text = ta.getString(R.styleable.CustomProperty_text); int textAttr = ta.getInteger(R.styleable.CustomProperty_number, -1); Log.e(TAG,"&&&第3个构造方法"+text+"-----"+textAttr); ta.recycle(); } public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); Log.e(TAG,"&&&第4个构造方法"); }}

9月9号 晚上

图片 7加入自定义属性后的Log信息

关于PorterDuffXfermode的坑总结的很好,PorterDuffXfermode不正确的真正原因

本以为会调用第3个构造方法,Log信息却显示,调用的依然是第2个构造方法,但此时已经拿到了自定义的属性

本人还很菜,有错误,请指出

结论2:在布局文件中使用自定义属性后,仍然调用的第2个构造方法

共勉 : )

在别的博客看到,想要调用到第3和4个构造方法,只有在第2个构造方法使用this进行调用

再次对代码进行修改:

//构造方法1this(context,null);//构造方法2this(context, attrs,0);//构造方法3this(context, attrs, defStyleAttr,0);

图片 8第2个构造方法调用第3和4个

此时就调用了第4和第3个构造方法,但最终还是会调用第2个构造方法

至于为啥这样设计构造方法,虽然看了几篇博客,我暂时依然没有想明白。难道是一开始系统默认会给一个属性,利用第3和第4个构造方法可以更改系统默认属性?

第一个和第二个参数都比较容易理解。

  • 第3个参数int defStyleAttr ,当前Theme中的包含的一个指向style的引用

    默认值为0,是一个属性资源。没有使用第2个参数给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值。传入0表示不向该defStyleAttr中查找默认值,只要在主题中对这个属性赋值,该View就会自动应用这个属性的值。

  • 第4个参数int defStyleRes,也就是在布局文件中使用的style="@style/..."id

    只有在第三个参数defStyleAttr为0,或者主题中没有找到这个defStyleAttr属性的赋值时,才可以启用。1.defStyleRes: 指向一个style引用.2.defStyleRes的优先级低于defStyleAttr.

这里,看得有些懵,先记住结论

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

以上摘抄Android自定义View构造函数详解

这里还找了另外两篇:Android View 四个构造函数详解Android中自定义样式与View的构造函数中的第三个参数defStyle的意义

这里实在是没有搞清楚优先级,先挖坑了 : )

虽然View也有好多类似Activity那种会在不同时期进行调用的方法,但实际上View的生命周期就3个阶段:

  1. 处理控件动画阶段
  2. 处理测量的阶段
  3. 处理绘制的阶段

重写了几个方法:

public class LifecycleView extends View { private final String TAG = "LifecycleView"; public LifecycleView(Context context) { this(context, null); Log.e(TAG, "&&&第1个构造方法"); } public LifecycleView(Context context, AttributeSet attrs) { super(context, attrs); Log.e(TAG, "&&&第2个构造方法"); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.e(TAG, "&&&解析布局文件onFinishInflate; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e(TAG, "&&&测量onMeasure; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); Log.e(TAG, "&&&布局onLayout; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Log.e(TAG, "&&&控件大小发生改变onSizeChanged; } @Override protected void onDraw(Canvas canvas) { super.onDraw; Log.e(TAG, "&&&绘制onDraw; } @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); Log.e(TAG, "&&&控件焦点改变onFocusChanged; } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); Log.e(TAG, "&&&获取或者失去焦点onWindowFocusChanged; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.e(TAG, "&&&开始附加在屏幕上onAttachedToWindow; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Log.e(TAG, "&&&与屏幕失去连接onDetachedFromWindow; } @Override protected void onAnimationStart() { super.onAnimationStart(); Log.e(TAG, "&&&动画开始onAnimationStart; } @Override protected void onAnimationEnd() { super.onAnimationEnd(); Log.e(TAG, "&&&动画结束onAnimationEnd; } @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); Log.e(TAG, "&&&可见性发生改变onWindowVisibilityChanged; }}

加载LifecycleView所在的Activity时:

图片 9显示LifecycleView

onFinishInflate()这个方法进行解析Viewxml文件,如果使用构造方法1,就不会回调这个方法。测量方法和布局方法会被回调好几次。而onDraw()竟然也回调了两次,一直觉得会被调用一次

点击回退键,结束掉Activity时:

图片 10关闭LifecycleView所在的Activity

关闭时倒是比较容易理解

关于构造方法34具体在哪种情景下会被调用,在哪个具体时机被调用没有搞明白,暂时决定先搁下,下篇打算开始学习事件体系。有知道的,请告诉一下

继承ViewGroup的知识点,在上篇已经有所了解。而继承现有的ViewViewGroup,和直接继承ViewViewGroup大体思路是一致的,甚至还简单一点

View的事件分发,涉及到责任链模式

本人很菜,有错误,请指出

共勉 : )

本文由澳门太阳娱乐集团官网发布于脚本专栏,转载请注明出处:Android 自定义View学习——PorterDuffXfermode简单练习

上一篇:Android签名 下一篇:没有了
猜你喜欢
热门排行
精彩图文