当前位置:   article > 正文

居中显示并旋转 android Button 里的属性drawableLeft_button drawableleft

button drawableleft

如图,点击同步按钮,同步图片要旋转起来,直到同步完毕。有一个容易实现的方法,就叫“方法1”吧(下面会用的),一个LinearLayout里面包含一个ImageView和一个TextView并且居中显示,监听LinearLayout的点击事件,然后旋转ImageView。

  1. <LinearLayout
  2. android:id="@+id/syn_now_layout"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:layout_marginTop="@dimen/address_clipboard_item_divider_padding"
  6. android:layout_marginLeft="@dimen/login_activity_padding"
  7. android:layout_marginRight="@dimen/login_activity_padding"
  8. android:layout_marginBottom="@dimen/address_clipboard_item_divider_padding"
  9. android:background="@color/download_apk_scanning"
  10. android:orientation="horizontal"
  11. android:gravity="center"
  12. android:paddingTop="10dp"
  13. android:paddingBottom="10dp"
  14. >
  15. <ImageView
  16. android:id="@+id/sync_image"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:src="@drawable/syn"/>
  20. <TextView
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:layout_marginLeft="5dp"
  24. android:duplicateParentState="true"
  25. android:textSize="15sp"
  26. android:textColor="@color/white"
  27. android:text="@string/sync_now"/>
  28. </LinearLayout>

  1. mSyncImage = (ImageView)findViewById(R.id.sync_image);
  2. findViewById(R.id.syn_now_layout).setOnClickListener(new OnClickListener() {
  3. @Override
  4. public void onClick(View view) {
  5. mSyncImage.clearAnimation();
  6. mSyncImage.startAnimation(getRotateAnimtion());
  7. }
  8. });
  9. /**
  10. * @return 旋转动画
  11. */
  12. private RotateAnimation getRotateAnimtion(){
  13. if(rAnimation==null)
  14. {
  15. rAnimation = new RotateAnimation(0, 359, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
  16. rAnimation.setDuration(1000);
  17. rAnimation.setRepeatCount(-1);
  18. rAnimation.setInterpolator(new LinearInterpolator());
  19. }
  20. return rAnimation;
  21. }



但是,在我知道要实现这个功能的时候,第一反应是通过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文件

  1. <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:drawable="@drawable/spinner_white_16"
  3. android:pivotX="50%"
  4. android:pivotY="50%"
  5. android:framesCount="12"
  6. android:frameDuration="100" />
模仿写试了试发现, android:framesCount与android:frameDuratiion 是内部属性,不能用。

根据animated-rotate我又找到AnimatedRotateDrawable类

  1. /*
  2. * Copyright (C) 2009 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package android.graphics.drawable;
  17. import android.graphics.Canvas;
  18. import android.graphics.Rect;
  19. import android.graphics.ColorFilter;
  20. import android.content.res.Resources;
  21. import android.content.res.TypedArray;
  22. import android.util.AttributeSet;
  23. import android.util.TypedValue;
  24. import android.util.Log;
  25. import android.os.SystemClock;
  26. import org.xmlpull.v1.XmlPullParser;
  27. import org.xmlpull.v1.XmlPullParserException;
  28. import java.io.IOException;
  29. import com.android.internal.R;
  30. /**
  31. * @hide
  32. */
  33. public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable,
  34. Animatable {
  35. private AnimatedRotateState mState;
  36. private boolean mMutated;
  37. private float mCurrentDegrees;
  38. private float mIncrement;
  39. private boolean mRunning;
  40. public AnimatedRotateDrawable() {
  41. this(null, null);
  42. }
  43. ·······
  44. }
看到了吧,又是隐藏类。但是我又看到了 AnimatedRotateDrawable实现了Animatable接口。

好像可以试一试,在工程的drawable文件下创建文件animated_rotate.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:drawable="@drawable/syn"
  4. android:pivotX="50%"
  5. android:pivotY="50%" />
然后

  1. <Button
  2. android:id="@+id/sync_now"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:drawableLeft="@drawable/animated_rotate"
  6. android:text="立即同步"/>

在Button的点击事件里处理旋转问题

  1. public void onClick(View v) {
  2. Button btnButton = (Button) v;
  3. // 获取android:drawableLeft
  4. Drawable drawable = btnButton.getCompoundDrawables()[0];
  5. if (!((Animatable) drawable).isRunning()) {
  6. ((Animatable) drawable).start();
  7. } else {
  8. ((Animatable) drawable).stop();
  9. }
  10. }

      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的

  1. // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
  2. // Make sure to update invalidateDrawable() when changing this code.
  3. if (dr.mDrawableLeft != null) {
  4. canvas.save();
  5. canvas.translate(scrollX + mPaddingLeft + leftOffset,
  6. scrollY + compoundPaddingTop +
  7. (vspace - dr.mDrawableHeightLeft) / 2);
  8. dr.mDrawableLeft.draw(canvas);
  9. canvas.restore();
  10. }

没有啥区别呀,先保存画布的状态,然后平移画布,画上图片,再恢复画布平移前的保存的状态。注意看上面的两段英文注释,虽然看到了invalidateDrawable函数,但是因为不了解就给忽略了。没有办法了,只能从动画开始一步一步分析吧。

动画开始,调用start() 方法,AnimatedRotateDrawable文件

  1. @Override
  2. public void start() {
  3. if (!mRunning) {
  4. mRunning = true;
  5. nextFrame();
  6. }
  7. }
  8. private void nextFrame() {
  9. unscheduleSelf(this);
  10. scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration);
  11. }
unscheduleSelf scheduleSelf 这两个函数干嘛?去父类Drawable看看实现

  1. public void unscheduleSelf(Runnable what) {
  2. final Callback callback = getCallback();
  3. if (callback != null) {
  4. callback.unscheduleDrawable(this, what);
  5. }
  6. public void scheduleSelf(Runnable what, long when) {
  7. final Callback callback = getCallback();
  8. if (callback != null) {
  9. callback.scheduleDrawable(this, what, when);
  10. }
  11. }

getCallback,那就有setCallback,Drawable的setCallback是在是在什么时候调用的呢,TextView 设置drawableLeft的函数是    public void setCompoundDrawables(Drawable left, Drawable top, right, Drawable bottom),其中有段代码是

  1. if (left != null) {
  2. left.setState(state);
  3. left.copyBounds(compoundRect);
  4. left.setCallback(this);
  5. dr.mDrawableSizeLeft = compoundRect.width();
  6. dr.mDrawableHeightLeft = compoundRect.height();
  7. } else {
  8. dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
  9. }

unscheduleDrawable  scheduleDrawable 的实现函数应该就在TextView里,查找后在父类里发现了实现方法

  1. /**
  2. * Schedules an action on a drawable to occur at a specified time.
  3. *
  4. * @param who the recipient of the action
  5. * @param what the action to run on the drawable
  6. * @param when the time at which the action must occur. Uses the
  7. * {@link SystemClock#uptimeMillis} timebase.
  8. */
  9. public void scheduleDrawable(Drawable who, Runnable what, long when) {
  10. if (verifyDrawable(who) && what != null) {
  11. final long delay = when - SystemClock.uptimeMillis();
  12. if (mAttachInfo != null) {
  13. mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
  14. Choreographer.CALLBACK_ANIMATION, what, who,
  15. Choreographer.subtractFrameDelay(delay));
  16. } else {
  17. ViewRootImpl.getRunQueue().postDelayed(what, delay);
  18. }
  19. }
  20. }
  21. /**
  22. * Cancels a scheduled action on a drawable.
  23. *
  24. * @param who the recipient of the action
  25. * @param what the action to cancel
  26. */
  27. public void unscheduleDrawable(Drawable who, Runnable what) {
  28. if (verifyDrawable(who) && what != null) {
  29. if (mAttachInfo != null) {
  30. mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
  31. Choreographer.CALLBACK_ANIMATION, what, who);
  32. } else {
  33. ViewRootImpl.getRunQueue().removeCallbacks(what);
  34. }
  35. }
  36. }


有没有发现什么,scheduleDrawable  函数中有ViewRootImpl.getRunQueue().postDelayed(what, delay);根据名字就能判断这是多长时间之后执行Runnable,也就是执行AnimatedRotateDrawable文件里的run()函数(不理解的查一下Runnable是个啥)。

因为what参数是AnimatedRotateDrawable类里scheduleSelf函数的第一个参数this,run函数实现

  1. @Override
  2. public void run() {
  3. // TODO: This should be computed in draw(Canvas), based on the amount
  4. // of time since the last frame drawn
  5. mCurrentDegrees += mIncrement;
  6. if (mCurrentDegrees > (360.0f - mIncrement)) {
  7. mCurrentDegrees = 0.0f;
  8. }
  9. invalidateSelf();
  10. nextFrame();
  11. }

nextFrame函数,又是一个循环。剩下的关键函数就是invalidateSelf,刷新,实现函数还是TextView里,因为Drawable里的实现方式是

  1. public void invalidateSelf() {
  2. final Callback callback = getCallback();
  3. if (callback != null) {
  4. callback.invalidateDrawable(this);
  5. }
  6. }

在TextView类里查找invalidateDrawable函数,去掉了 mDrawableRight 等代码

  1. @Override
  2. public void invalidateDrawable(Drawable drawable) {
  3. if (verifyDrawable(drawable)) {
  4. final Rect dirty = drawable.getBounds();
  5. int scrollX = mScrollX;
  6. int scrollY = mScrollY;
  7. // IMPORTANT: The coordinates below are based on the coordinates computed
  8. // for each compound drawable in onDraw(). Make sure to update each section
  9. // accordingly.
  10. final TextView.Drawables drawables = mDrawables;
  11. if (drawables != null) {
  12. if (drawable == drawables.mDrawableLeft) {
  13. final int compoundPaddingTop = getCompoundPaddingTop();
  14. final int compoundPaddingBottom = getCompoundPaddingBottom();
  15. final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
  16. scrollX += mPaddingLeft;
  17. scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
  18. }
  19. ········
  20. ········
  21. }
  22. invalidate(dirty.left + scrollX, dirty.top + scrollY,
  23. dirty.right + scrollX, dirty.bottom + scrollY);
  24. }
  25. }

invalidate函数不陌生吧,一部分客户区域将被重新绘制。跟到这里,就明白问题出在哪里了,因为动画是要不断的重新绘制的,但是我自定义的类里没有重写invalidateDrawable函数,导致调用了父类TextView里的 invalidateDrawable。重新绘制的区域错了,所以点击按钮后,图片不旋转。

自定义类重写的函数是两个onDraw(Canvas canvas) 和 invalidateDrawable(Drawable drawable)

自定义类文件DrawableCenterButton.java

  1. public class DrawableCenterButton extends TextView{
  2. public DrawableCenterButton(Context context) {
  3. super(context);
  4. }
  5. public DrawableCenterButton(Context context, AttributeSet attrs, int defStyle) {
  6. super(context, attrs, defStyle);
  7. }
  8. public DrawableCenterButton(Context context, AttributeSet attrs) {
  9. super(context, attrs);
  10. }
  11. int textWidth;
  12. int textHeight;
  13. Drawable mDrawableLeft = null;
  14. int startDrawableX = 0;
  15. int startDrawableY= 0;
  16. @Override
  17. protected void onFinishInflate() {
  18. super.onFinishInflate();
  19. initText();
  20. }
  21. private void initText() {
  22. String textStr = super.getText().toString();
  23. Rect rect = new Rect();
  24. getPaint().getTextBounds(textStr,0,textStr.length(),rect);
  25. getPaint().setColor(getTextColors().getDefaultColor());
  26. getPaint().setTextSize(getTextSize());
  27. textWidth = rect.width();
  28. textHeight = rect.height();
  29. }
  30. @Override
  31. protected void onDraw(Canvas canvas) {
  32. if (mDrawableLeft == null) mDrawableLeft = getCompoundDrawables()[0];
  33. if (mDrawableLeft == null) {
  34. super.onDraw(canvas);
  35. return;
  36. }
  37. int drawablePadding = getCompoundDrawablePadding();
  38. int drawableWidth = this.mDrawableLeft.getIntrinsicWidth();
  39. int drawableHeight = this.mDrawableLeft.getIntrinsicHeight();
  40. startDrawableX = (getWidth() >> 1) - ((drawablePadding + textWidth + drawableWidth) >> 1);
  41. startDrawableY = (getHeight() >> 1) - (drawableHeight >> 1);
  42. //画旋转图片
  43. canvas.save();
  44. canvas.translate(startDrawableX, startDrawableY);
  45. this.mDrawableLeft.draw(canvas);
  46. canvas.restore();
  47. //画文字
  48. int boxht = this.getMeasuredHeight() - this.getExtendedPaddingTop() - this.getExtendedPaddingBottom();
  49. int textht = getLayout().getHeight();
  50. int voffsetText = boxht - textht >> 1;
  51. canvas.save();
  52. canvas.translate((float) (startDrawableX + drawableWidth + drawablePadding), (float) (getExtendedPaddingTop() + voffsetText));
  53. getLayout().draw(canvas);
  54. canvas.restore();
  55. }
  56. @Override
  57. public void invalidateDrawable(Drawable drawable) {
  58. // super.invalidateDrawable(drawable);
  59. final Rect dirty = drawable.getBounds();
  60. int scrollX = 0;
  61. int scrollY = 0;
  62. if(drawable == this.mDrawableLeft){
  63. scrollX = startDrawableX;
  64. scrollY = startDrawableY;
  65. }
  66. this.invalidate(dirty.left + scrollX-2, dirty.top + scrollY-2, dirty.right + scrollX+2, dirty.bottom + scrollY+2);
  67. }
  68. public Drawable getDrawableLeft() {
  69. return mDrawableLeft;
  70. }
  71. }


成功!!!!


源码


参考:

关于TextView 宽度过大导致Drawable无法居中问题

http://blog.csdn.net/freesonhp/article/details/32695163

自定义控件让TextView的drawableLeft与文本一起居中显示

http://www.cnblogs.com/over140/p/3464348.html

View编程(3): invalidate()源码分析


本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号