最近接了个任务,在小部件(AppWidget)中播放动画。android中小部件是使用RemoteViews加载布局。但RemoteViews支持的View很少,并且不支持自定义View。
如果让RemoteViews支持自定义View,需要在将该View放到framewoke下编译。现记录实现过程如下:
1.自定义View,将class文件放到\frameworks\base\core\java\android\widget/目录下,并在代码第一行添加@RemoteViews.RemoteView标签。代码如下:
自定义的RobotAnimView是一个动画类,主要增加播放动画方法。
package android.widget; import android.R; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.RemoteViews; @RemoteViews.RemoteView public class RobotAnimView extends View { private WaveRobotAnimator mAnimator; public RobotAnimView(Context context) { super(context); init(); } public RobotAnimView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public RobotAnimView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public void startRobotAnimator(boolean start) { if (start) { startRobotAnimator(); } else { endRobotAnimator(); } } public void startRobotAnimator() { init(); mAnimator.start(); } public void endRobotAnimator() { if (mAnimator != null) { if (mAnimator.isRunning()) { mAnimator.end(); } mAnimator = null; } } private void init() { endRobotAnimator(); mAnimator = new WaveRobotAnimator(getContext(), false); this.setBackground(mAnimator.getDrawable()); } private class WaveRobotAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener { private RobotDrawable mDrawable; WaveRobotAnimator(Context context, boolean left) { this.setDuration(1700); this.setIntValues(0, 1700); this.setInterpolator(new LinearInterpolator()); mDrawable = new RobotDrawable(context, left); addUpdateListener(this); } @Override public void onAnimationUpdate(ValueAnimator animation) { mDrawable.update((int) (animation.getAnimatedValue())); mDrawable.invalidateSelf(); } Drawable getDrawable() { return mDrawable; } private class RobotDrawable extends Drawable { private int mTime; private Body mBody; private EyeLeft mEyeLeft; private EyeRight mEyeRight; private HandLeft mHandLeft; private HandRight mHandRight; private Head mHead; private Mouth mMouth; private int mWidth; private int mHeight; private boolean mIsLeft; RobotDrawable(Context context, boolean left) { mIsLeft = left; mBody = new Body(context); mEyeLeft = new EyeLeft(context); mEyeRight = new EyeRight(context); mHandLeft = new HandLeft(context); mHandRight = new HandRight(context); mHead = new Head(context); mMouth = new Mouth(context); } @Override public void setAlpha(int alpha) { } private void update(int time) { mTime = time; } @Override public void draw(Canvas canvas) { if (mWidth == 0 || mHeight == 0) { mWidth = canvas.getWidth(); mHeight = canvas.getHeight(); } canvas.save(); if (mIsLeft) { canvas.scale(-1, 1, canvas.getWidth() / 2f, canvas.getHeight() / 2f); } canvas.translate(mWidth / 2f - 126 / 2, mHeight / 2f - 164 / 2); float bodyProgress = mBody.getProgress(mTime); canvas.rotate(bodyProgress, 63, 164); canvas.save(); float headProgress = mHead.getProgress(mTime); canvas.rotate(headProgress, 63, 103); mHead.draw(canvas); mEyeLeft.draw(canvas, mTime); mEyeRight.draw(canvas, mTime); mMouth.draw(canvas); canvas.restore(); mBody.draw(canvas); mHandLeft.draw(canvas, mTime); mHandRight.draw(canvas, mTime); canvas.restore(); } @Override public void setColorFilter(ColorFilter colorFilter) { } @Override public int getOpacity() { return PixelFormat.UNKNOWN; } } private class Body extends Basic { private final Interpolator[] mInterpolator = { new EaseCubicInterpolator(0.48f, 0.06f, 0.52f, 0.94f), new EaseCubicInterpolator(0.47f, 0.00f, 0.77f, 0.63f), new EaseCubicInterpolator(0.34f, -0.68f, 0.55f, -1.14f), new EaseCubicInterpolator(0.48f, 0.00f, 0.52f, 1.00f) }; private final float[] mProgress = {0f, 18f, 17.7f, 18.0f}; Body(Context context) { mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_body); mBitmap = Bitmap.createScaledBitmap(mBitmap, 57, 59, true); } private void draw(Canvas canvas) { mMatrix.setTranslate(34, 103); canvas.drawBitmap(mBitmap, mMatrix, null); } float getProgress(int time) { return getProgress(time, mTimes, mProgress, mInterpolator); } } private class EyeLeft extends Eye { EyeLeft(Context context) { mTransX = 28; mTransY = 57; mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_eye_left); mBitmap = Bitmap.createScaledBitmap(mBitmap, 18, 22, true); } } private class EyeRight extends Eye { EyeRight(Context context) { mTransX = 80; mTransY = 57; mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_eye_right); mBitmap = Bitmap.createScaledBitmap(mBitmap, 18, 22, true); } } class Eye extends Basic { final float[] mTimes = {50f, 200f}; final float[] mProgress = {1f, 0.15f, 1f}; final Interpolator[] mInterpolator = { new EaseCubicInterpolator(0.33f, 0.00f, 0.67f, 1.00f), new EaseCubicInterpolator(0.33f, 0.00f, 0.67f, 1.00f) }; int mTransX; int mTransY; void draw(Canvas canvas, int time) { mMatrix.setTranslate(mTransX, mTransY); if (time >= 1000 && time <= 1250) { float scale = getProgress(time - 1000, mTimes, mProgress, mInterpolator); mMatrix.postScale(1, scale, mTransX + mBitmap.getWidth() / 2f, mTransY + mBitmap.getHeight() / 2f); } canvas.drawBitmap(mBitmap, mMatrix, null); } } private class HandLeft extends Basic { private final float[] mProgress = {0f, 116f, 80f, 116f}; private final Interpolator[] mInterpolator = { new EaseCubicInterpolator(0.44f, 0.20f, 0.71f, 1.00f), new EaseCubicInterpolator(0.48f, 0.00f, 0.44f, 1.00f), new EaseCubicInterpolator(0.53f, 0.00f, 0.61f, 1.00f), new EaseCubicInterpolator(0.17f, 0.00f, 0.52f, 1.00f) }; HandLeft(Context context) { mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_hand_left); mBitmap = Bitmap.createScaledBitmap(mBitmap, 19, 27, true); } private void draw(Canvas canvas, int time) { float progress = getProgress(time, mTimes, mProgress, mInterpolator); mMatrix.setTranslate(16, 116); mMatrix.postRotate(progress, 37, 114); canvas.drawBitmap(mBitmap, mMatrix, null); } } private class HandRight extends Basic { private final float[] mProgress = {0f, -25f, -24.6f, -25f}; private final Interpolator[] mInterpolator = { new EaseCubicInterpolator(0.48f, 0.06f, 0.52f, 0.94f), new EaseCubicInterpolator(0.47f, 0.00f, 0.77f, 0.63f), new EaseCubicInterpolator(0.34f, -0.68f, 0.55f, -1.14f), new EaseCubicInterpolator(0.48f, 0.00f, 0.52f, 1.00f) }; HandRight(Context context) { mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_hand_right); mBitmap = Bitmap.createScaledBitmap(mBitmap, 19, 27, true); } private void draw(Canvas canvas, int time) { float progress = getProgress(time, mTimes, mProgress, mInterpolator); mMatrix.setTranslate(90, 116); mMatrix.postRotate(progress, 88, 114); canvas.drawBitmap(mBitmap, mMatrix, null); } } private class Head extends Basic { private final float[] mProgress = {0f, 18f, 17.7f, 18f}; private final Interpolator[] mInterpolator = { new EaseCubicInterpolator(0.48f, 0.60f, 0.52f, 0.94f), new EaseCubicInterpolator(0.47f, 0.00f, 0.77f, 0.63f), new EaseCubicInterpolator(0.34f, -0.68f, 0.55f, -1.14f), new EaseCubicInterpolator(0.48f, 0.00f, 0.52f, 1.00f) }; Head(Context context) { mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_head); mBitmap = Bitmap.createScaledBitmap(mBitmap, 126, 103, true); } private void draw(Canvas canvas) { canvas.drawBitmap(mBitmap, mMatrix, null); } float getProgress(int time) { return getProgress(time, mTimes, mProgress, mInterpolator); } } private class Mouth extends Basic { Mouth(Context context) { mBitmap = BitmapFactory.decodeResource(context.getResources(), com.android.internal.R.drawable.anim_robot_mouth); mBitmap = Bitmap.createScaledBitmap(mBitmap, 12, 8, true); } private void draw(Canvas canvas) { mMatrix.setTranslate(57, 81); canvas.drawBitmap(mBitmap, mMatrix, null); } } private class Basic { final float[] mTimes = {700f, 200f, 250f, 550f}; Bitmap mBitmap; Matrix mMatrix = new Matrix(); float getProgress(int time, float[] times, float[] progresss, Interpolator[] interpolators) { float progress = 0f; float t; if (time < times[0]) { t = 1f * time / times[0]; progress = progresss[0] + interpolators[0].getInterpolation(t) * (progresss[1] - progresss[0]); } else if (time < times[0] + times[1]) { t = 1f * (time - times[0]) / times[1]; progress = progresss[1] + interpolators[1].getInterpolation(t) * (progresss[2] - progresss[1]); } else if (time < times[0] + times[1] + times[2]) { t = 1f * (time - times[1] - times[0]) / times[2]; progress = progresss[2] + interpolators[2].getInterpolation(t) * (progresss[3] - progresss[2]); } else if (time < times[0] + times[1] + times[2] + times[3]) { t = 1f * (time - times[2] - times[1] - times[0]) / times[3]; progress = progresss[3] + interpolators[3].getInterpolation(t) * (0 - progresss[3]); } return progress; } } } private class EaseCubicInterpolator implements Interpolator { private final static int ACCURACY = 4096; private static final float STEP_SIZE = 1.0f / ACCURACY; private int mLastI = 0; private final PointF mControlPoint1 = new PointF(); private final PointF mControlPoint2 = new PointF(); EaseCubicInterpolator(float x1, float y1, float x2, float y2) { mControlPoint1.x = x1; mControlPoint1.y = y1; mControlPoint2.x = x2; mControlPoint2.y = y2; } @Override public float getInterpolation(float input) { float t = input; for (int i = mLastI; i < ACCURACY; i++) { t = i * STEP_SIZE; double x = cubicCurves(t, 0, mControlPoint1.x, mControlPoint2.x, 1); if (x >= input) { mLastI = i; break; } } double value = cubicCurves(t, 0, mControlPoint1.y, mControlPoint2.y, 1); if (value == 1) { mLastI = 0; } return (float) value; } double cubicCurves(double t, double value0, double value1, double value2, double value3) { double value; double u = 1 - t; double tt = t * t; double uu = u * u; double uuu = uu * u; double ttt = tt * t; value = uuu * value0; value += 3 * uu * t * value1; value += 3 * u * tt * value2; value += ttt * value3; return value; } } }二、自定义View中需要引入bitmap资源。使用R.drawable时,参考TextView源码,使用com.android.internal.R.drawable.形式引入。
三、png资源文件放到\frameworks\base\core\res\res\drawable\目录下。
只将图片放到drawable目录下编译时编不过,会找不到图片资源。参考TextView源码,将用到的资源在frameworks/base/core/res/res/values/symbols.xml中声明。查资料得知,自己添加的资源要在public.xml(资源可对外使用)或symbols.xml(资源私有)中声明。
代码片段:
四、编译framework代码,将生成的framework.jar和framew-res.apk push到手机中。Remoteview就可以使用自定义View了。小部件如何用RemoteView后面再更新。