您的位置:首页 > 其它

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

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
类,例如:

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自定义控件其实很简单
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息