属性动画、事件分发和自定义控件
2015-06-10 10:10
489 查看
这里写了三个大的方面希望记录一下。当然,最大的收获是发现Aige的博客专栏,自定义控件其实很简单。在那里弄懂了很多,也同样看到了自己的距离。但这些可能并不是意识到就能够有变化的。我的观点观点和他的一样,自定义控件,首先要会画,要能画当然要掌握基本的方法。而往往,看到一大堆的方法,好似懂了一两个用法,就不愿去耐心尝试下其它的用法致手上的工具不足。而Aige讲解的控件的测量方面,也是获益匪浅,但其中的ViewGroup部分还留待去消,导化实践。
先从最简单的记起,事件分发。事件分发是从ViewGroup开始传递的。首先是从 ViewGroup#dispatchTouchEvent 方法开始的,也就是事件分发。如果其中 ViewGroup#onInterceptTouchEvent 方法返回true,就把事件在这里拦截,不再去寻找对应的子控件,而是直接在自己ViewGroup的 super#dispatchTouchEvent 中进行处理。onInterceptTouchEvent()
方法是ViewGroup独有的,它的作用是对事件进行拦截,默认返回false。如果ViewGroup#onInterceptTouchEvent 返回false,那么在接下来的代码实现中,就会去寻找对应的子控件,然后进入到该控件的View#dispatchTouchEvent。那么当找到子控件或者ViewGroup自己处理,最后都是到了View#dispatchTouchEVent 方法中。该方法首先调用onTouch()方法,如果有注册触摸事件监听的话。如果onTouch 方法返回true,那么事件分发就结束了。如果返回的不是true,那么就会执行onTouchEvent
方法。onTouchEvent 方法中对ACTION_DOWN、ACTION_UP,等事件进行处理,并在ACTION_UP中进行一段的逻辑判断后,执行 performClick() 方法,也就是执行我们注册的 onClick() 方法。
属性动画之前也认真的学过,结果前一两天要写时,却忘记得较干脆。之前在ToggleButton中也提到过rebound库可能和插值器这部分密切相关,这次重新学习时,发现正是其中的机制。先看基本的使用,主要是ObjectAnmator
类,例如:
如果要对动画一起播放,顺序播放,可以使用AnimatorSet 类的相关方法。同样,也可以在xml中定义动画。在参考博文中有说明举例。
接下来看ValueAnimator的用法,属性动画的原理是对数值进行变化,然后用该变化的数值对控件的属性进行设置更新。ValueAnimator类中的有个注册监听方法:
我们可以通过animation#getAnimatedValue() 获取到变化的对象(可能是整型值,可能是任何其它对象)。那么它的变化规律是在哪里如何控制的呢?它是在一个实现TypeEvaluator的接口的类中传递过去的。TypeEvaluator 类型估值器只有一个方法:
我们在这里的返回值,就是在监听接口用 getAnimatorValue 方法得到的值。参数fraction,范围在0到1,表示的动画的进度百分比;参数startValue和嗯对Value表示的传进来起止对象(不一定是数值对象),从哪里传进来呢,后面的例子中可以明显看到。那么在这里我们就可以做映射处理。那么还有个问题,就是fraction是以怎样的规律传进来呢?这里就涉及到另一个概念,就是插值器Interpolator的使用。系统有默认的实现和配置。这些类的共同点都是实现了 TimeInterpolator接口。可以在Eclispe中直接查看或查找文档。
TimeInterpolator 接口:
只有一个接口方法,input是匀速的时间流逝比例。返回值就是我们的fraction值。在这里我们可以实现各种不同的数率变化,比如简单的匀速变化:
或者其它很复杂的模型。
参考博文中,给出了一个例子,这里我也自己实现了,贴下相关的代码:
代码都很简单,或者在上面都解释过了。
自定义控件,首先要画一个漂亮的控件。如果作画时以设计的角度去思考如何实现,可能会更简单一些。Paint,画笔。可以设置各种属性,比如 setColorFilter,对图片的颜色的处理,可以变化成各种风格,比如怀旧老照片,变暗,色彩增强等;比如 setXfermode,对两张图片进行各种混合处理,通过Alpha通道合成,它的各种模式都注释有计算公式。通过 setXfermode 可以实现多种效果;比如 setMaskFilter,可以设置过滤的效果,比如边缘的模糊处理;比如
setPathEffect,可以设置路径的连接方式;比如 setShader,可以设置画笔的遮罩模式,可以实现探照灯、圆形头像提取;等等等。Canvas,画布的容器,图形的承载者。可以缩放,平移,分层。
有些累,不写了。其中跟着Aige实现了大部分,这里贴两个图,一个是图标连接,另一个是模拟水杯的效果;这个用高阶贝塞尔曲线来完成的,和360的内存球的效果很类似。有空要练习,可以考虑实现360的内存球效果。
①
②
其中,该博文中对View的测量的流程,以及为什么要这样做,讲解的非常清楚。
参考博文:
1. Android 事件分发机制详解
2. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
3. Android属性动画深入分析:让你成为动画牛人
4. Android属性动画完全解析(上),初识属性动画的基本用法
5. android动画(一)Interpolator
6. Android自定义控件其实很简单
先从最简单的记起,事件分发。事件分发是从ViewGroup开始传递的。首先是从 ViewGroup#dispatchTouchEvent 方法开始的,也就是事件分发。如果其中 ViewGroup#onInterceptTouchEvent 方法返回true,就把事件在这里拦截,不再去寻找对应的子控件,而是直接在自己ViewGroup的 super#dispatchTouchEvent 中进行处理。onInterceptTouchEvent()
方法是ViewGroup独有的,它的作用是对事件进行拦截,默认返回false。如果ViewGroup#onInterceptTouchEvent 返回false,那么在接下来的代码实现中,就会去寻找对应的子控件,然后进入到该控件的View#dispatchTouchEvent。那么当找到子控件或者ViewGroup自己处理,最后都是到了View#dispatchTouchEVent 方法中。该方法首先调用onTouch()方法,如果有注册触摸事件监听的话。如果onTouch 方法返回true,那么事件分发就结束了。如果返回的不是true,那么就会执行onTouchEvent
方法。onTouchEvent 方法中对ACTION_DOWN、ACTION_UP,等事件进行处理,并在ACTION_UP中进行一段的逻辑判断后,执行 performClick() 方法,也就是执行我们注册的 onClick() 方法。
属性动画之前也认真的学过,结果前一两天要写时,却忘记得较干脆。之前在ToggleButton中也提到过rebound库可能和插值器这部分密切相关,这次重新学习时,发现正是其中的机制。先看基本的使用,主要是ObjectAnmator
类,例如:
ValueAnimator anim = ObjectAnimator.ofInt(mImageView, "translationY", mHeight); anim.setDuration(5000) .setInterpolator(new AccelerateDecelerateInterpolator()); anim.setRepeatCount(4); anim.setRepeatMode(ValueAnimator.REVERSE); anim.addListener(new Animator.AnimatorListener(){ @Override public void onAnimationStart(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationCancel(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationRepeat(Animator animation) { // TODO Auto-generated method stub } }); anim.start();
如果要对动画一起播放,顺序播放,可以使用AnimatorSet 类的相关方法。同样,也可以在xml中定义动画。在参考博文中有说明举例。
接下来看ValueAnimator的用法,属性动画的原理是对数值进行变化,然后用该变化的数值对控件的属性进行设置更新。ValueAnimator类中的有个注册监听方法:
public void addUpdateListener(AnimatorUpdateListener listener)它的参数是个接口,在接口的回调方法中,
public static interface AnimatorUpdateListener { /** * <p>Notifies the occurrence of another frame of the animation.</p> * * @param animation The animation which was repeated. */ void onAnimationUpdate(ValueAnimator animation); }
我们可以通过animation#getAnimatedValue() 获取到变化的对象(可能是整型值,可能是任何其它对象)。那么它的变化规律是在哪里如何控制的呢?它是在一个实现TypeEvaluator的接口的类中传递过去的。TypeEvaluator 类型估值器只有一个方法:
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); }
我们在这里的返回值,就是在监听接口用 getAnimatorValue 方法得到的值。参数fraction,范围在0到1,表示的动画的进度百分比;参数startValue和嗯对Value表示的传进来起止对象(不一定是数值对象),从哪里传进来呢,后面的例子中可以明显看到。那么在这里我们就可以做映射处理。那么还有个问题,就是fraction是以怎样的规律传进来呢?这里就涉及到另一个概念,就是插值器Interpolator的使用。系统有默认的实现和配置。这些类的共同点都是实现了 TimeInterpolator接口。可以在Eclispe中直接查看或查找文档。
TimeInterpolator 接口:
public interface TimeInterpolator { float getInterpolation(float input); }
只有一个接口方法,input是匀速的时间流逝比例。返回值就是我们的fraction值。在这里我们可以实现各种不同的数率变化,比如简单的匀速变化:
/** * An interpolator where the rate of change is constant * */ public class LinearInterpolator implements Interpolator { public LinearInterpolator() { } public LinearInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return input; } }
或者其它很复杂的模型。
参考博文中,给出了一个例子,这里我也自己实现了,贴下相关的代码:
BallPoint endBallPoint = new BallPoint(mWidth/2, mHeight-mRadius, Color.RED); BallPoint startBallPoint = new BallPoint(mWidth/2, mRadius, Color.BLUE); mCurrentBallPoint = startBallPoint; ofObject = ValueAnimator.ofObject(new BallPointEvaluator(), startBallPoint, endBallPoint); ofObject.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { BallPoint animatedValue = (BallPoint) animation.getAnimatedValue(); mCurrentBallPoint = animatedValue; mBallPointPaint.setColor(mCurrentBallPoint.getColor()); log("F", "mcurrentBallPoint:" + mCurrentBallPoint.toString()); invalidate(); } }); ofObject.setDuration(3000); ofObject.setRepeatCount(3); ofObject.setRepeatMode(ValueAnimator.RESTART); // ofObject.setInterpolator(new BounceInterpolator()); ofObject.setInterpolator(new MyInterpolator()); ofObject.start();
private class BallPointEvaluator implements TypeEvaluator<BallPoint> { @Override public BallPoint evaluate(float fraction, BallPoint startValue, BallPoint endValue) { int x = (int) (startValue.getX() + fraction * (endValue.getX() - startValue.getX())); int y = (int) (startValue.getY() + fraction * (endV 4000 alue.getY() - startValue.getY())); int oriColor = startValue.getColor(); int dstColor = endValue.getColor(); int or = Color.red(oriColor); int og = Color.green(oriColor); int ob = Color.blue(oriColor); int dr = Color.red(dstColor); int dg = Color.green(dstColor); int db = Color.blue(dstColor); int diffr = (int) (or + fraction * (dr - or)); int diffg = (int) (og + fraction * (dg - og)); int diffb = (int) (ob + fraction * (db - ob)); int diffColor = Color.rgb(diffr, diffg, diffb); BallPoint ballPoint = new BallPoint(x, y, diffColor); return ballPoint; } }
private class MyInterpolator implements TimeInterpolator { @Override public float getInterpolation(float input) { float f = input * input; return f; } }
代码都很简单,或者在上面都解释过了。
自定义控件,首先要画一个漂亮的控件。如果作画时以设计的角度去思考如何实现,可能会更简单一些。Paint,画笔。可以设置各种属性,比如 setColorFilter,对图片的颜色的处理,可以变化成各种风格,比如怀旧老照片,变暗,色彩增强等;比如 setXfermode,对两张图片进行各种混合处理,通过Alpha通道合成,它的各种模式都注释有计算公式。通过 setXfermode 可以实现多种效果;比如 setMaskFilter,可以设置过滤的效果,比如边缘的模糊处理;比如
setPathEffect,可以设置路径的连接方式;比如 setShader,可以设置画笔的遮罩模式,可以实现探照灯、圆形头像提取;等等等。Canvas,画布的容器,图形的承载者。可以缩放,平移,分层。
有些累,不写了。其中跟着Aige实现了大部分,这里贴两个图,一个是图标连接,另一个是模拟水杯的效果;这个用高阶贝塞尔曲线来完成的,和360的内存球的效果很类似。有空要练习,可以考虑实现360的内存球效果。
①
private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mCircleWidth); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mTextPaint.setTextSize(18); mTextPaint.setColor(Color.WHITE); mTextPaint.setTextAlign(Paint.Align.CENTER); mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mArcPaint.setStyle(Paint.Style.FILL); mArcPaint.setColor(0x65ec6941); mArcPaint.setStrokeWidth(mCircleWidth); FontMetrics fontMetrics = mTextPaint.getFontMetrics(); // mTextAlignOffestY = (fontMetrics.descent - fontMetrics.ascent ) / 4; //这里怎么会是除以4呢?!,错误 mTextAlignOffestY = Math.abs(fontMetrics.ascent) / 2 - fontMetrics.descent /2 ; //正确 log("ascent:" + mTextPaint.getFontMetrics().ascent); log("descent:" + mTextPaint.getFontMetrics().descent ); log("mTextAlignOffestY:" + mTextAlignOffestY); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.parseColor("#FF8C00")); mPaint.setColor(Color.WHITE); //中间的圆 canvas.drawCircle(mWidth/2, mHeight/2+mRadius, mRadius, mPaint); canvas.drawText("AigeStudio", mWidth/2, mHeight/2+mRadius+mTextAlignOffestY, mTextPaint); // canvas.drawLine(0, mHeight/2+mRadius, mWidth, mHeight/2+mRadius, mPaint); // canvas.drawLine(0, mHeight/2+mRadius+mTextAlignOffestY, mWidth, mHeight/2+mRadius+mTextAlignOffestY, mPaint); //画左上角的两个圆 canvas.save(); canvas.rotate(mDegrees, mWidth/2, mHeight/2+mRadius); canvas.drawLine(mWidth/2, mHeight/2, mWidth/2, mHeight/2-mLineLength, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-mRadius, mRadius, mPaint); canvas.drawText("loveever", mWidth/2, mHeight/2-mLineLength-mRadius+mTextAlignOffestY, mTextPaint); canvas.drawLine(mWidth/2, mHeight/2-mLineLength-2*mRadius, mWidth/2, mHeight/2-mLineLength-2*mRadius-mLineLength, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-2*mRadius-mLineLength-mRadius, mRadius, mPaint); canvas.drawText("mylife", mWidth/2, mHeight/2-mLineLength-2*mRadius-mLineLength-mRadius+mTextAlignOffestY, mTextPaint); canvas.restore(); //画右上角的圆 canvas.save(); canvas.rotate(-mDegrees, mWidth/2, mHeight/2+mRadius); canvas.drawLine(mWidth/2, mHeight/2, mWidth/2, mHeight/2-mLineLength, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-mRadius, mRadius, mPaint); canvas.drawText("somethings", mWidth/2, mHeight/2-mLineLength-mRadius+mTextAlignOffestY, mTextPaint); float rectOffestXY = mRadius + mRadius/2; RectF oval = new RectF(mWidth/2-rectOffestXY, mHeight/2-mLineLength-mRadius-rectOffestXY, mWidth/2+rectOffestXY, mHeight/2-mLineLength-mRadius+rectOffestXY); canvas.drawArc(oval, -150f, 120f, true, mArcPaint); mArcPaint.setColor(Color.WHITE); mArcPaint.setStyle(Paint.Style.STROKE); canvas.drawArc(oval, -150f, 120f, false, mArcPaint); float baseArc = 120 / 3; float offestArc = 30; float centerX = mWidth/2; float centerY = mHeight/2-mLineLength-mRadius; drawArcText(canvas, "Milk", rectOffestXY, offestArc, -1, centerX, centerY); drawArcText(canvas, "FFFFF", rectOffestXY, offestArc+baseArc, -1, centerX, centerY); drawArcText(canvas, "Yes", rectOffestXY, offestArc, 1, centerX, centerY); drawArcText(canvas, "Love", rectOffestXY, offestArc+offestArc, 1, centerX, centerY); canvas.restore(); //画左边的圆 canvas.save(); canvas.rotate(mDegrees+(2*mDegrees)+mDegrees*0.5f, mWidth/2, mHeight/2+mRadius); canvas.drawLine(mWidth/2, mHeight/2-mMarginSpace, mWidth/2, mHeight/2-mLineLength-mMarginSpace, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2, mRadiusSmall, mPaint); canvas.drawText("Apple", mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2+mTextAlignOffestY, mTextPaint); canvas.restore(); //画下边的圆 canvas.save(); canvas.rotate(6*mDegrees, mWidth/2, mHeight/2+mRadius); canvas.drawLine(mWidth/2, mHeight/2-mMarginSpace, mWidth/2, mHeight/2-mLineLength-mMarginSpace, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2, mRadiusSmall, mPaint); canvas.drawText("Pear", mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2+mTextAlignOffestY, mTextPaint); canvas.restore(); //画右边的圆 canvas.save(); canvas.rotate(-mDegrees-(2*mDegrees)-mDegrees*0.5f, mWidth/2, mHeight/2+mRadius); canvas.drawLine(mWidth/2, mHeight/2-mMarginSpace, mWidth/2, mHeight/2-mLineLength-mMarginSpace, mPaint); canvas.drawCircle(mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2, mRadiusSmall, mPaint); canvas.drawText("Orange", mWidth/2, mHeight/2-mLineLength-mRadiusSmall-mMarginSpace*2+mTextAlignOffestY, mTextPaint); canvas.restore(); }
private void drawArcText(Canvas canvas, String string, float radius, float degress, int flag, float centerX, float centerY) { log("String:" + string + ", out radius:" + radius + ", degress:" + degress + ", flag: " + flag + ", centerX:" + centerX + ", centerY" + centerY); float disX = (float) (Math.cos(degress*Math.PI/180)*radius); float disY = (float) (Math.sin(degress*Math.PI/180)*radius) + 10; float x = (float) (disX * flag + centerX); float y = (float) (-disY + centerY); canvas.drawText(string, x, y, mTextPaint); log("disX:" + disX + ", disY:" + disY); }
②
@Override protected void init() { mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPathPaint.setColor(Color.GREEN); mPathPaint.setStyle(Paint.Style.FILL); mPath = new Path(); }
@Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.LTGRAY); mPath.reset(); mPath.moveTo(-mWidth /4, mCurrentY); mPath.quadTo(mCurrentX, mCurrentY-mWaterHeightOffset, mWidth+mWidth/4, mCurrentY); mPath.lineTo(mWidth+mWidth/4, mHeight); mPath.lineTo(-mWidth/4, mHeight); mPath.close(); mCurrentY = mInitY + mIncY++; if (mCurrentY >= mHeight ) { mIncY = (int) 1; } if (mCurrentX >= mWidth ) { isRight = false; } else if (mCurrentX <= 0 ) { isRight = true; } mCurrentX = isRight ? mCurrentX+40 : mCurrentX-40; canvas.drawPath(mPath, mPathPaint); // log("F", "mCurrentX:" + mCurrentX + ", mCurrentY:" + mCurrentY); invalidate(); }
其中,该博文中对View的测量的流程,以及为什么要这样做,讲解的非常清楚。
参考博文:
1. Android 事件分发机制详解
2. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
3. Android属性动画深入分析:让你成为动画牛人
4. Android属性动画完全解析(上),初识属性动画的基本用法
5. android动画(一)Interpolator
6. Android自定义控件其实很简单
相关文章推荐
- 麻雀虽小五脏俱全 Dojo自定义控件应用
- WinForm自定义控件应用实例
- 百度地图自定义控件分享
- 用javascript添加控件自定义属性解析
- Android自定义控件之仿优酷菜单
- Android自定义表格控件满足人们对视觉的需求
- asp.net 自定义控件实现无刷新上传图片,立即显示缩略图,保存图片缩略图
- asp.net DropDownList自定义控件,让你的分类更清晰
- 如何通过javascript操作web控件的自定义属性
- 自定义模板列在 PostBack 后消失的问题
- android自定义控件实例
- android中播放gif动画之二
- android面试常见的handler机制 AIDL机制 高级控件UI 内存优化
- Android Zxing条码扫描自定义控件(附代码)
- winform控件显示及闪烁问题
- android 事件分发机制完全解析 从源码开始(上)
- android 事件分发机制完全解析 从源码开始(下)
- 自定义控件-1.基本用法
- Android自定义控件之旅(一)滑动开关
- 第二章(3)自定义控件