如图,点击同步按钮,同步图片要旋转起来,直到同步完毕。有一个容易实现的方法,就叫“方法1”吧(下面会用的),一个LinearLayout里面包含一个ImageView和一个TextView并且居中显示,监听LinearLayout的点击事件,然后旋转ImageView。
mSyncImage = (ImageView)findViewById(R.id.sync_image);
findViewById(R.id.syn_now_layout).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mSyncImage.clearAnimation();
mSyncImage.startAnimation(getRotateAnimtion());
}
});
/**
* @return 旋转动画
*/
private RotateAnimation getRotateAnimtion(){
if(rAnimation==null)
{
rAnimation = new RotateAnimation(0, 359, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rAnimation.setDuration(1000);
rAnimation.setRepeatCount(-1);
rAnimation.setInterpolator(new LinearInterpolator());
}
return rAnimation;
}
但是,在我知道要实现这个功能的时候,第一反应是通过Button的drawableLeft属性实现(一个按钮上同时显示图片和文字),我想没有深入了解过drawableLeft属性的人(比如我)大部分都会是我这个想法的。真的去实现的时候才发现,我擦,居然有两大难点,一是从Button中取出图片的drawable,不知道怎么对drawable对象做动画,一是drawableLeft 图片不居中显示
1. drawableLeft 取出来后,怎么旋转呢
通过Google知道系统的圆形进度条的图片是spinner_white_16.png,用到sdk/platforms/android-22/data/res/drawable/progress_small_white.xml文件
模仿写试了试发现,
android:framesCount与android:frameDuratiion 是内部属性,不能用。
根据animated-rotate我又找到AnimatedRotateDrawable类
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.drawable;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.ColorFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Log;
import android.os.SystemClock;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import com.android.internal.R;
/**
* @hide
*/
public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable,
Animatable {
private AnimatedRotateState mState;
private boolean mMutated;
private float mCurrentDegrees;
private float mIncrement;
private boolean mRunning;
public AnimatedRotateDrawable() {
this(null, null);
}
·······
}
看到了吧,又是隐藏类。但是我又看到了
AnimatedRotateDrawable实现了Animatable接口。
好像可以试一试,在工程的drawable文件下创建文件animated_rotate.xml
然后
在Button的点击事件里处理旋转问题
public void onClick(View v) {
Button btnButton = (Button) v;
// 获取android:drawableLeft
Drawable drawable = btnButton.getCompoundDrawables()[0];
if (!((Animatable) drawable).isRunning()) {
((Animatable) drawable).start();
} else {
((Animatable) drawable).stop();
}
}
2.drawableLeft 图片不居中显示:
解决办法两种,一种是Button的android:layout_width和android:layout_height的属性设为wrap_content,然后外面在包一层LinearLayout ,设置属性 android:gravity="center",然后监听LinearLayout点击事件,咦,这不和“方法1”一样了吗,换第二种方法 ,第二种方法就是自定义控件了。通过网络查到两种结局办法,
一种是文字图片都在左边,然后在 onDraw 函数里向右平移画布(canvas.translate),调用父级onDraw去画。
一种是自己写onDraw方法,在Canvas画布的某个位置去画图和文字,不在调用父级onDraw。这个种方式比上一个麻烦,但是实现了normal状态和press状态的图片切换。
两种方式都试过之后,图片和文字都居中显示了,但是当点击按钮后出了一个问题,图片不旋转了。去掉我在onDraw里写的代码,直接调用super.onDraw(canvas) 图片旋转就没问题。通过查看AnimatedRotateDrawable 和 TextView的源码才明白我应该重写public void invalidateDrawable(Drawable drawable) 方法。(Button的父类是TextView,drawableLeft的属性就是在TextView里实现的)
首先看TextView里的onDraw是怎么实现的,其中有段代码是画drawableLeft的
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mDrawableLeft != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
dr.mDrawableLeft.draw(canvas);
canvas.restore();
}
动画开始,调用start() 方法,AnimatedRotateDrawable文件
@Override
public void start() {
if (!mRunning) {
mRunning = true;
nextFrame();
}
}
private void nextFrame() {
unscheduleSelf(this);
scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration);
}
unscheduleSelf scheduleSelf 这两个函数干嘛?去父类Drawable看看实现
public void unscheduleSelf(Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
public void scheduleSelf(Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
if (left != null) {
left.setState(state);
left.copyBounds(compoundRect);
left.setCallback(this);
dr.mDrawableSizeLeft = compoundRect.width();
dr.mDrawableHeightLeft = compoundRect.height();
} else {
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
}
unscheduleDrawable scheduleDrawable 的实现函数应该就在TextView里,查找后在父类里发现了实现方法
/**
* Schedules an action on a drawable to occur at a specified time.
*
* @param who the recipient of the action
* @param what the action to run on the drawable
* @param when the time at which the action must occur. Uses the
* {@link SystemClock#uptimeMillis} timebase.
*/
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (verifyDrawable(who) && what != null) {
final long delay = when - SystemClock.uptimeMillis();
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, what, who,
Choreographer.subtractFrameDelay(delay));
} else {
ViewRootImpl.getRunQueue().postDelayed(what, delay);
}
}
}
/**
* Cancels a scheduled action on a drawable.
*
* @param who the recipient of the action
* @param what the action to cancel
*/
public void unscheduleDrawable(Drawable who, Runnable what) {
if (verifyDrawable(who) && what != null) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, what, who);
} else {
ViewRootImpl.getRunQueue().removeCallbacks(what);
}
}
}
有没有发现什么,scheduleDrawable 函数中有ViewRootImpl.getRunQueue().postDelayed(what, delay);根据名字就能判断这是多长时间之后执行Runnable,也就是执行AnimatedRotateDrawable文件里的run()函数(不理解的查一下Runnable是个啥)。
因为what参数是AnimatedRotateDrawable类里scheduleSelf函数的第一个参数this,run函数实现
@Override
public void run() {
// TODO: This should be computed in draw(Canvas), based on the amount
// of time since the last frame drawn
mCurrentDegrees += mIncrement;
if (mCurrentDegrees > (360.0f - mIncrement)) {
mCurrentDegrees = 0.0f;
}
invalidateSelf();
nextFrame();
}
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
@Override
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
int scrollX = mScrollX;
int scrollY = mScrollY;
// IMPORTANT: The coordinates below are based on the coordinates computed
// for each compound drawable in onDraw(). Make sure to update each section
// accordingly.
final TextView.Drawables drawables = mDrawables;
if (drawables != null) {
if (drawable == drawables.mDrawableLeft) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
scrollX += mPaddingLeft;
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
}
········
········
}
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
}
}
自定义类重写的函数是两个onDraw(Canvas canvas) 和 invalidateDrawable(Drawable drawable)
自定义类文件DrawableCenterButton.java
public class DrawableCenterButton extends TextView{
public DrawableCenterButton(Context context) {
super(context);
}
public DrawableCenterButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public DrawableCenterButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
int textWidth;
int textHeight;
Drawable mDrawableLeft = null;
int startDrawableX = 0;
int startDrawableY= 0;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
initText();
}
private void initText() {
String textStr = super.getText().toString();
Rect rect = new Rect();
getPaint().getTextBounds(textStr,0,textStr.length(),rect);
getPaint().setColor(getTextColors().getDefaultColor());
getPaint().setTextSize(getTextSize());
textWidth = rect.width();
textHeight = rect.height();
}
@Override
protected void onDraw(Canvas canvas) {
if (mDrawableLeft == null) mDrawableLeft = getCompoundDrawables()[0];
if (mDrawableLeft == null) {
super.onDraw(canvas);
return;
}
int drawablePadding = getCompoundDrawablePadding();
int drawableWidth = this.mDrawableLeft.getIntrinsicWidth();
int drawableHeight = this.mDrawableLeft.getIntrinsicHeight();
startDrawableX = (getWidth() >> 1) - ((drawablePadding + textWidth + drawableWidth) >> 1);
startDrawableY = (getHeight() >> 1) - (drawableHeight >> 1);
//画旋转图片
canvas.save();
canvas.translate(startDrawableX, startDrawableY);
this.mDrawableLeft.draw(canvas);
canvas.restore();
//画文字
int boxht = this.getMeasuredHeight() - this.getExtendedPaddingTop() - this.getExtendedPaddingBottom();
int textht = getLayout().getHeight();
int voffsetText = boxht - textht >> 1;
canvas.save();
canvas.translate((float) (startDrawableX + drawableWidth + drawablePadding), (float) (getExtendedPaddingTop() + voffsetText));
getLayout().draw(canvas);
canvas.restore();
}
@Override
public void invalidateDrawable(Drawable drawable) {
// super.invalidateDrawable(drawable);
final Rect dirty = drawable.getBounds();
int scrollX = 0;
int scrollY = 0;
if(drawable == this.mDrawableLeft){
scrollX = startDrawableX;
scrollY = startDrawableY;
}
this.invalidate(dirty.left + scrollX-2, dirty.top + scrollY-2, dirty.right + scrollX+2, dirty.bottom + scrollY+2);
}
public Drawable getDrawableLeft() {
return mDrawableLeft;
}
}
成功!!!!
参考:
关于TextView 宽度过大导致Drawable无法居中问题
http://blog.csdn.net/freesonhp/article/details/32695163
自定义控件让TextView的drawableLeft与文本一起居中显示
http://www.cnblogs.com/over140/p/3464348.html
View编程(3): invalidate()源码分析