android仿winphone进度指示器的实现
转载注明出处。
这里说的效果指的是winphone的忽快忽慢的小圆点(恩,你懂得吧= =)。分为转圈和直线两种。在网上看到有人实现了直线的,于是下下来源码看了一下,顺带实现了转圈的。感觉还算好看。在这里做个记录。顺便谢谢那个慷慨开源的哥们儿~~、
先说说直线运动小圆点的原理吧。定义一个数组分别对于小圆点,再定义一个同样大小的数组规定每个点的Visibility情况。由后者控制每个点的可见性。定义直线上3/10的区域是慢速区域,其他为快速区。开启一个线程,线程每运行一次画面刷新一次。每个点依次出发,之间有一定时间间隔。根据点运行的距离判断下一次点应该在哪。下面是代码。在原作的基础上我给出了更详细的注释。读者应该能一遍看懂了~工程文件我就不提供了,大家自己实现吧。
WP7ProgressView
1 package com.example.dialog; 2 3 import android.content.Context; 4 import android.graphics.Canvas; 5 import android.graphics.Color; 6 import android.graphics.Paint; 7 import android.graphics.RectF; 8 import android.os.Handler; 9 import android.util.AttributeSet; 10 import android.util.Log; 11 import android.view.View; 12 13 public class WP7ProgessView extends View { 14 15 private int mColor = Color.RED; 16 private int mBackgroundColor = Color.BLACK; 17 private Paint mPainter = null; 18 19 private static final int RECT_NUMBER = 5; 20 private static int[] sPos = new int[RECT_NUMBER]; 21 private static int[] sVisible = new int[RECT_NUMBER]; 22 23 private static final int RECT_WIDTH = 5; // px 24 25 private static final float SLOW_RANGE = 0.30f; // 有30%的空间是慢的 26 27 private static final int UPDATE_PROGRESS_INTERVAL = 28; // ms 28 29 private static final int STANDARD_SCREEN_W = 480; // px 30 31 private UpdateProgessThread mThread = null; 32 33 public WP7ProgessView(Context context) { 34 this(context, null); 35 } 36 37 public WP7ProgessView(Context context, AttributeSet attrs) { 38 this(context, attrs, 0); 39 } 40 41 public WP7ProgessView(Context context, AttributeSet attrs, int defStyle) { 42 super(context, attrs, defStyle); 43 } 44 45 public WP7ProgessView(Context context, int color) { 46 this(context, null, 0); 47 mColor = color; 48 mPainter = new Paint(); 49 mPainter.setAntiAlias(true); 50 mPainter.setColor(mColor); 51 mPainter.setStyle(Paint.Style.FILL); 52 setBackgroundColor(mBackgroundColor); 53 } 54 55 public void setColor(int color) { 56 mColor = color; 57 mPainter.setColor(mColor); 58 } 59 60 public int getColor() { 61 return mColor; 62 } 63 64 @Override 65 public void draw(Canvas canvas) { 66 canvas.drawColor(mBackgroundColor); 67 super.draw(canvas); 68 69 for (int i = 0; i < RECT_NUMBER; i++) { 70 if (sVisible[i] == View.VISIBLE) { 71 drawRect(canvas, sPos[i]); 72 } 73 } 74 } 75 76 private void drawRect(Canvas canvas, int pos) { 77 int m = getHeight() / 2; 78 int w = RECT_WIDTH / 2; 79 canvas.drawRect(new RectF(pos - w, m - w, pos + w, m + w), mPainter); 80 } 81 82 @Override 83 protected void onAttachedToWindow() { 84 super.onAttachedToWindow(); 85 mThread = new UpdateProgessThread("WP7ProgessView", this); 86 mThread.start(); 87 } 88 89 @Override 90 protected void onDetachedFromWindow() { 91 mThread.stopping(); 92 mThread = null; 93 super.onDetachedFromWindow(); 94 } 95 96 @Override 97 protected void onWindowVisibilityChanged(int visibility) { 98 // TODO Auto-generated method stub 99 super.onWindowVisibilityChanged(visibility); 100 } 101 102 private static final class UpdateProgessThread extends Thread { 103 private WP7ProgessView host = null;//就理解为宿主吧。。 104 private int mInvisibleCount = 0; // 控制是不是重新开始一次循环,当都不可见了则说明需要重新开始了 105 private int mPosToShowNext = 0; // 显示下一个方块坐标 106 private int mIndexToShowNext = 0; // 检查显示下一个方块的方块坐标,如果为0则表示刚刚开始 107 108 private long mSleepTime = 0; 109 110 private boolean running = true; 111 112 public UpdateProgessThread(String name, WP7ProgessView v) { 113 super(name); 114 host = v; 115 for (int i = 0; i < RECT_NUMBER; i++) { 116 117 // 让所有的小方块不显示 118 sPos[i] = -RECT_WIDTH; 119 sVisible[i] = View.GONE; 120 } 121 122 // 至少先让第一个可见 123 sVisible[0] = View.VISIBLE; 124 } 125 126 public void run() { 127 int w = host.getWidth(); 128 Log.d("XXXXX", "w = " + w); 129 int slowRange = 0; 130 int fastRange1 = 0; 131 132 while (running) { 133 if (w <= 0) {//一般不会小于0只会等于0.此时这个view还没有被绘制,以下是准备操作 134 w = host.getWidth(); 135 slowRange = (int) (w * SLOW_RANGE); 136 fastRange1 = (w - slowRange) / 2; 137 Log.e("width", w + " " + slowRange + " " + fastRange1); 138 } 139 if (w > 0 && mSleepTime <= 0) { 140 mSleepTime = (long) (UPDATE_PROGRESS_INTERVAL 141 * STANDARD_SCREEN_W / w); 142 } 143 // int fastRange2 = fastRange1; 144 145 if (mPosToShowNext == 0) { 146 /** 147 * 现在我们来决定五个小方框的出现时机,研究了Windows Phone 7的progress 148 * dialog,它的效果是 149 * 至少能让五个小方块在同时出现在慢区域。所以在第一个方块走出慢区域的时候,第五个必然要进入慢区域. 150 * 算法是五个方块都要在慢区,所以他们的间隔是 slowRange/6, 快区是慢区的4/3倍,所以显示间隔就是 151 * slowRange * (1/6) * (4/3)。 152 */ 153 mPosToShowNext = (int) (slowRange * 2.0 / 9); 154 } 155 156 if (mIndexToShowNext < RECT_NUMBER - 1 ) { 157 /** 158 * 如果指定的方块跑过了显示下一个方块的点,则显示下一个方块。 159 */ 160 //因为第一个点已经是可见的,所以是RECT_NUMBER - 1 161 if (sPos[mIndexToShowNext] > mPosToShowNext) { 162 mIndexToShowNext++; 163 sVisible[mIndexToShowNext] = View.VISIBLE; 164 Log.e("index", "mIndexToShowNext:" + mIndexToShowNext); 165 } 166 } 167 168 try { 169 // 更新界面的时间间隔。 170 Thread.sleep(mSleepTime); 171 } catch (InterruptedException e) { 172 e.printStackTrace(); 173 } 174 175 for (int i = 0; i < RECT_NUMBER; i++) { 176 if (sVisible[i] != View.VISIBLE) { 177 // 如果这个点是看不见的,则不更新了。 178 Log.e("i", i + ""); 179 continue; 180 } 181 if (sPos[i] <= fastRange1 182 || sPos[i] >= (fastRange1 + slowRange)) { 183 // 让它移动快点。 184 sPos[i] += (2 * RECT_WIDTH); 185 } else if ((sPos[i] - fastRange1) < slowRange) { 186 // 让它移动慢点。 187 sPos[i] += RECT_WIDTH; 188 } 189 // 如果已经移出边界,则让它看不见 190 if (sPos[i] > w) { 191 sVisible[i] = View.GONE; 192 sPos[i] = -RECT_WIDTH; 193 } 194 } 195 196 mInvisibleCount = 0; 197 198 for (int i = 0; i < RECT_NUMBER; i++) { 199 if (sVisible[i] != View.VISIBLE) { 200 mInvisibleCount++; 201 } 202 } 203 204 // 如果需要重新开始,则初始化一些变量。 205 if (mInvisibleCount >= RECT_NUMBER) { 206 mIndexToShowNext = 0; 207 sVisible[0] = View.VISIBLE; 208 } 209 210 host.postInvalidate(); 211 } 212 } 213 214 public void stopping() { 215 running = false;//方便控制线程运行与否。 216 // stop(); 217 } 218 } 219 220 }
同时提供我稍作修改的一个Circle类。它是转圆圈的ProgressView。这个Circle类是一个自定义View,在每次返回键被按了时再进去会重新启动一个线程,看起来就是圈圈转的更快了。还有就是我的onMeasure方法没有重写,,懒得写了。所以在drawCircle
方法里你会看到我x,y坐标都加了十,那是为了偏离边界好看清楚我的圆。。。如果要用你可以自行修改这俩小bug.其它的我还没发现。其实bug1在每次activity的onStop()里把线程stopThread()一下就好了。下面是代码。这个注释就少了。可以参照上面看。主要变化的就是把他的距离变成了我的弧度,他只跑一圈我为了和winphone一样让小球跑两圈。恩请看代码:
Cirlcle
1 package com.example.dialogcircle; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.graphics.Canvas; 6 import android.graphics.Color; 7 import android.graphics.Paint; 8 import android.util.AttributeSet; 9 import android.util.Log; 10 import android.view.View; 11 12 public class Circle extends View{ 13 14 private static final int UPDATE_PROGRESS_INTERVAL = 30;//ms 15 private static final float DEGREE = (float) 720.0;//一周360度 16 private static final float SLOWRANGE = (float) (360.0 * 0.3f);//慢的空间 17 private static final float FASTRANGE = (float) ((360.0 - SLOWRANGE) / 2);//慢的空间 18 private static final float SLICE = 5;//每次递增度数为10度 19 private static final float RADIUS = 5;//小圆的半径 20 21 private int mColor = Color.WHITE; 22 private float mRadius = 0; 23 24 25 private Paint mPainter = null; 26 27 private static int dotNum = 4; 28 29 private static float[] degOfDot; 30 private static int[] visibleDot; 31 32 private Context mContext; 33 34 public static UpdateThread mThread = null; 35 36 public Circle(Context context) { 37 super(context); 38 } 39 40 public Circle(Context context, AttributeSet attrs){ 41 super(context, attrs); 42 TypedArray paramsArray = context.obtainStyledAttributes(attrs, 43 R.styleable.Circle); 44 mColor = paramsArray.getColor(R.styleable.Circle_color, Color.WHITE); 45 mRadius = paramsArray.getFloat(R.styleable.Circle_radius, 120.0f); 46 dotNum = paramsArray.getInt(R.styleable.Circle_dotNum, 4); 47 48 mPainter = new Paint(); 49 mPainter.setAntiAlias(true); 50 mPainter.setStyle(Paint.Style.FILL); 51 mPainter.setColor(mColor); 52 53 degOfDot = new float[dotNum]; 54 visibleDot = new int[dotNum]; 55 paramsArray.recycle(); 56 57 mThread = new UpdateThread(this); 58 mThread.start(); 59 if(!mThread.isAlive())mThread.notify(); 60 } 61 public Circle(Context context, AttributeSet attrs, int defStyle){ 62 super(context, attrs, defStyle); 63 } 64 65 public void draw(Canvas canvas){ 66 super.draw(canvas); 67 for(int i = 0; i < dotNum; i++){ 68 if(visibleDot[i] == View.VISIBLE){ 69 drawCircle(canvas, degOfDot[i]); 70 } 71 } 72 } 73 private void drawCircle(Canvas canvas, float deg){ 74 float cx = 0, cy = 0; 75 cx = (float) (mRadius * ( 1.0 + Math.sin( deg / 180.0 * Math.PI))) + 10; 76 cy = (float) (mRadius * ( 1.0 - Math.cos( deg / 180.0 * Math.PI))) + 10; 77 canvas.drawCircle(cx, cy , RADIUS, mPainter); 78 } 79 80 81 static final class UpdateThread extends Thread{ 82 private Circle host = null; 83 private int mInvisibleCount = 0;//是否重新开始循环 84 private float mDegToShowNext = 0;//下一个圆角度 85 private int mIndexToShowNext = 0; 86 87 private long mSleepTime = UPDATE_PROGRESS_INTERVAL; 88 89 private boolean running = true; 90 91 public UpdateThread(Circle c){ 92 super(); 93 host = c; 94 for(int i = 0; i < dotNum; i++){ 95 degOfDot[i] = 0; 96 visibleDot[i] = View.GONE; 97 } 98 visibleDot[0] = View.VISIBLE; 99 } 100 public void run(){ 101 while(running){ 102 if(mDegToShowNext == 0){ 103 mDegToShowNext = (float)(SLOWRANGE * 2.0 / 9); 104 } 105 if(mIndexToShowNext < dotNum - 1){ 106 if(degOfDot[mIndexToShowNext] > mDegToShowNext){ 107 mIndexToShowNext++; 108 visibleDot[mIndexToShowNext] = View.VISIBLE; 109 } 110 } 111 try { 112 Thread.sleep(mSleepTime); 113 } catch (InterruptedException e) { 114 // TODO: handle exception 115 e.printStackTrace(); 116 } 117 118 for(int i = 0; i < dotNum; i++){ 119 if(visibleDot[i] != View.VISIBLE){ 120 continue; 121 } 122 //以下是运动范围分类 123 if(degOfDot[i] <= FASTRANGE 124 || (degOfDot[i] >= (FASTRANGE + SLOWRANGE) 125 && degOfDot[i] < (360.0 + FASTRANGE / 2.0)) 126 || (degOfDot[i] >= (360.0 + FASTRANGE / 2.0 + SLOWRANGE)) 127 &&(degOfDot[i] < 720.0) ){ 128 degOfDot[i] += (2.0 * SLICE); 129 }else if ((degOfDot[i] < FASTRANGE + SLOWRANGE) 130 && (degOfDot[i] > FASTRANGE) 131 || (degOfDot[i] < (360.0 + FASTRANGE / 2.0 + SLOWRANGE)) 132 && (degOfDot[i] >= (360.0 + FASTRANGE / 2.0))){ 133 // 让它移动慢点。 134 degOfDot[i] += SLICE; 135 } 136 if(degOfDot[i] >= DEGREE){ 137 visibleDot[i] = View.GONE; 138 degOfDot[i] = 0; 139 } 140 } 141 142 mInvisibleCount = 0; 143 144 for(int i = 0; i < dotNum; i++){ 145 if(visibleDot[i] != View.VISIBLE){ 146 mInvisibleCount++; 147 } 148 } 149 if(mInvisibleCount >= dotNum){ 150 mIndexToShowNext = 0; 151 visibleDot[0] = View.VISIBLE; 152 } 153 host.postInvalidate(); 154 } 155 } 156 public void stopThread(){ 157 running = false; 158 } 159 } 160 }
有任何问题请留言。为了大家的可持续发展我不提供源代码。程序员不能懒啊><