属性动画、事件分发和自定义控件

这里写了三个大的方面希望记录一下。当然,最大的收获是发现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 类,例如:

		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 * (endValue.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自定义控件其实很简单






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值