Android里已经有足够多的控件供开发者使用,但有时候我们还是会想要一些不一样的东西,比如一些UI特效,比如一些3D动画,今天就讲讲比较basic的东西:自定义控件。

1.效果图

如果项目里需要一个通用的控件,然后UI给你这样一个效果图,你接下来会打算怎么做?

用户可以按住拖动

点击要切换的状态,然后自动滑动到那一端

(本来是没有这个效果图的,又不想一张张贴不同的状态,就画了一下这个gif图,关于怎么在ubuntu下画gif图,可以看一下下面这篇)

程序媛也会画图 之 在ubuntu下用GIMP制作gif

2.分析

看一下有没有现成的widget,这似乎和android.widget.Switch有点类似,可是Swithc是水平的,水平没有关系,改成垂直的问题不大,先来尝试下好了,就先把背景和button的图片换一下,来看一下结果是怎样:

额。。。这个切换似乎生硬了点,没有渐变的动画。好吧,那还是重新自己写一个控件吧。

3.创建Andriod自定义控件的步骤

怎么建立一个自定义的控件,说起来并不难,有三个内容需要实现:

3.1新建一个控件类,继承android.view.View类:

 1 public class XXXView extends View { 2     ... 3     protected void onDraw(Canvas canvas) { 4         ... 5     } 6      7     public boolean onTouchEvent(MotionEvent event) { 8         ... 9     }10 11     public interface OnXXXListener { //状态回调,同View.OnClickListener12         public abstract void xxx();13         public abstract void xxx();14     }15 }

3.2 在布局文件xml里使用这个控件:

<com.xxx.xxx.XXXView  android:id=”@+id/xxx”    android:layoutWidth=”...”    android:layoutHeight=”...”></com.xxx.xxx.XXXView>

3.3 在Activity类里获得这个控件:

1 mXXXView = (XXXView) findViewById(R.id.xxx);2 mXXXView.setListener(mXXXViewListener);

以上这简单的3个步骤就是创建和使用控件的内容了,到这里,如果你是个喜欢着急写代码的人,你也可以先搭一个程序框架出来跑跑看啦。

4.考虑怎么画?
4.1拖动
用户需要能拖动Button,那也就是说我们在控件里需要捕获用户的touch event,知道用户到底是做了什么动作(ACTION_DOWN, ACTION_MOVE, UP), 还有操作的位置在哪里(getX(), getY()).
这些信息从哪里可以知道?--》onTouchEvent()回调!

4.2动画
动画的本质就是图片+位置+时间差。
在效果图中,用户也可以点击一个状态,让控件滑动。那这个滑动的过程就是一个动画的。
图片我们有,那怎么把图片画到Canvas上?-》在onDraw()回调里面画。在主线程里只要调用invalidate(),就会重新触发onDraw()的执行。如果我们在一定的时间间隔,在不同的位置重新画图片,不就是动画了?
位置可以从用户行为获得,或者自己计算;
时间差,在Android里面控制时间最容易的是什么?当然是Handler啦,因为它可以发送delay的消息。

4.3渐变的实现
效果图中还有个渐变的过程,这个看起来好像蛮麻烦,其实也好办。因为有Alpha的存在。我们可以在画的时候根据不同的位置,设置Paint不同的Alpha值,一个图片Alpha慢慢减小,另一个图片Alpha慢慢增大。

ok,分析到这里,就大概知道该怎么做了,在onTouchEvent()回调里,获得用户的行为和位置,并记录下来,在适当的时候发送Message给Handler,或者直接调用invalidate()重新画。在Handler里,接收到信息,就根据当前的状态,更新图片下一个应该出现的位置,然后调用invalidate()触发重新画。


5.计算位置
ok,上面已经确定以什么方式做了,接下来就要用到一点点数学的计算了。我们要确定图片从哪里开始动,动到哪里结束,还有在什么位置开始切换状态。


先切下图:

(文字也做成了图片,其实凡是涉及到文字的都不应该做成图片,如果有人切换到中文,然后他又不认识on off呢,而且这些文字应该要可设置的才对。这里图方便就做成图片了。)

然后就是一些重要坐标位置啦:

图1 蓝色是那个长条的图片,绿色两块是在两个状态下Button所在的位置。

图2 黄色的区域是两个小的灰色文字图片

图3 这个区域就是文字开始切换的区域

6.伪代码

现在方法也有了,数据也有了,就可以开始写代码了。
为了叙述方便,就用伪代码代替了,下面是最重要的三个部分的伪码:

处理用户行为的逻辑:

 1 public boolean onTouchEvent(MotionEvent event) {  //处理用户行为 2     case ACTION_DOWN: 3         if (坐标在图1中蓝色区域) { //touch在无效的区域 4             return; 5         } 6  7         if (坐标在图1中绿色区域中Button在的区域) { //当前状态是on,就是上面的区域,否则,就是下面的区域 8             获得坐标与上边缘的距离gap; 9         } else {10             设置正在滑动标志;11             设置动画的方向,发送Message;  //会执行到这里的情况是,比如当前状态是on,用户点击了off那一端,那接下来控件就要自动滑动切换到off状态。12         }13          break;14     15     case ACTION_MOVE:16          if (上次Down是在无效区域 | 正在切换状态) { //此时不用响应Move动作。17              return;18          }19 20          if (根据当前的坐标计算,滑块将不在背景区域) {21              return;22          }23 24          if (根据当前的坐标计算,在文字交换的区域) {25              设置交换标记;26          }27          记录滑块当前位置;28          invalidate();29          break;30 31     case ACTION_UP:32          if (上次Down是在无效区域 | 正在切换状态) { //此时不用响应Up动作。33              return;34          }35  36          取消交换标识;37          if(根据当前坐标计算,最后的状态是on) {38             设置滑块位置为on状态时的位置;39             修改状态为on;40             invalidate();41          } else {42             设置滑块位置为off状态时的位置;43             修改状态为off;44             invalidate();45          }46 }

处理自动滑动:

 1 private Handler mHandler = new Handler() {  //用于处理自动滑动那部分逻辑 2     public void handleMessage(Message msg) { 3          if (计数 > 20) { 4              设置当前状态; 5              设置滑块的位置; 6              取消正在滑动的标志; 7              计数归0; 8              return 9          }10 11          根据计数,获得interpolator.getInterpolation;//这里用了AccelerateDecelerateInterpolator,让动画有一个加速的效果,其实这么短的距离效果看不出来。12          计算滑块的位置;13          invalidate();14          计数+115          sendMessageDelayed(0, 20); //20ms后画下一帧。16     }17 18 };

画:

 1 protected void onDraw(Canvas canvas) { //具体画的代码 2      画背景; 3  4      if (在状态交换区域) { 5          根据滑块位置这是Paint的Alpha值; 6          用上面设置的Paint画那四个小图; //在状态交换的时候,四个小图都是显示的。 7      } else { 8          根据当前的状态,画on滑块或off滑块; 9      }10 }

ok,有上面3部分的内容,基本上就可以了。

下面就是运行起来的效果,(不好表示啦,其实就是效果图那样的)

贴个对应的代码段:

Handler:

 1     private Handler mHandler = new Handler() { 2         @Override 3         public void handleMessage(Message msg) { 4             if (drawCount > 20) { 5                 if (button_status == STATUS_OFF) { 6                     button_status = STATUS_ON; 7                     buttonY = buttonTopY; 8                     if (listener != null) { 9                         listener.slipToTop();10                     }11                 } else {12                     button_status = STATUS_OFF;13                     buttonY = buttonBottomY;14                     if (listener != null) {15                         listener.slipToBottom();16                     }17                 }18 19                 isTouchDownAnotherSide = false;20                 drawCount = 0;21                 return;22             }23 24             float p;25             if (isToBottom) {26                 p = (float) (drawCount * 0.05);27             } else {28                p = (float) (1 - drawCount * 0.05);29             }30             float inter = interpolator.getInterpolation(p);31             buttonY = buttonTopY + (buttonBottomY - buttonTopY) * inter;32 33             if (buttonY >= exchangeBeginY && buttonY <= exchangeEndY) {34                 isExchange = true;35             } else {36                 isExchange = false;37             }38             invalidate();39             drawCount++;40             sendEmptyMessageDelayed(0, 20);41         }42     };
View Code

onDraw():

 1     @Override 2     protected void onDraw(Canvas canvas) { 3         // TODO Auto-generated method stub 4         canvas.drawBitmap(mBackBitmap, 0, 0, null); 5  6         if (isExchange) { 7             // in exchange area, we should set alpha 8             Paint mPaint = new Paint(); 9 10             int alpha = (int) (255 - 255 * (buttonY - 25.5) / 50);11             mPaint.setAlpha(alpha);12             canvas.drawBitmap(mONBitmap, buttonTopX, buttonY, mPaint);13             canvas.drawBitmap(mOFFTextBitmap, textBottomX, textBottomY, mPaint);14 15             mPaint.setAlpha(255 - alpha);16             canvas.drawBitmap(mOFFBitmap, buttonBottomX, buttonY, mPaint);17             canvas.drawBitmap(mONTextBitmap, textTopX, textTopY, mPaint);18         } else {19             if (getNearLocation(0, buttonY) == STATUS_ON) {20                 canvas.drawBitmap(mONBitmap, buttonTopX, buttonY, null);21                 canvas.drawBitmap(mOFFTextBitmap, textBottomX, textBottomY, null);22             } else {23                 canvas.drawBitmap(mOFFBitmap, buttonBottomX, buttonY, null);24                 canvas.drawBitmap(mONTextBitmap, textTopX, textTopY, null);25             }26         }27     }
View Code

onTouchEvent():

  1     @Override  2     public boolean onTouchEvent(MotionEvent event) {  3         // TODO Auto-generated method stub  4   5         float x = event.getX();  6         float y = event.getY();  7         switch (event.getAction()) {  8         case MotionEvent.ACTION_DOWN:  9  10             if (isTouchDownAnotherSide) { 11                 return true; 12             } 13  14             // check if touch right place 15             if (isOutOfFrontBitmap(x, y)) { 16                 isTouchDownValid = false; 17                 return true; 18             } 19  20             if (listener != null) { 21                 listener.touchedDown(); 22             } 23  24             if (isInFrontBitmap(x, y)) { 25                 // touch in current mode 26                 Log.e("Slip", "ACTION_DOWN : yes! infrontBitmap"); 27                 isTouchDownValid = true; 28                 touchDownGap = getGap(x, y); 29             } else { 30                 // touch anther side 31                 Log.e("Slip", "ACTION_DOWN : no! infrontBitmap"); 32                 isTouchDownValid = false; 33                 isTouchDownAnotherSide = true; 34                 if (button_status == STATUS_ON) { 35                     isToBottom = true; 36                     mHandler.sendEmptyMessage(0); 37                 } else { 38                     isToBottom = false; 39                     mHandler.sendEmptyMessage(0); 40                 } 41             } 42             break; 43         case MotionEvent.ACTION_MOVE: 44             // if touch down wrong place, we ignore next action 45             if (!isTouchDownValid || isTouchDownAnotherSide) { 46                 return true; 47             } 48             if (!isInBackBitmap(x, y)) { 49                 Log.e("Slip", "ACTION_MOVE : no! isInBackBitmap"); 50                 return true; 51             } 52             if (isInExchangeArea(x, y)) { 53                 isExchange = true; 54             } else { 55                 isExchange = false; 56             } 57             buttonY = y - touchDownGap; 58             this.invalidate(); 59  60             break; 61         case MotionEvent.ACTION_UP: 62             // if touch down wrong place, we ignore next action 63             if (!isTouchDownValid || isTouchDownAnotherSide) { 64                 Log.e("Slip", "ACTION_UP : no! isTouchDownValid"); 65                 return true; 66             } 67  68             isExchange = false; 69  70             if (getFinalLocation(x, y) == STATUS_ON) { 71                 buttonY = buttonTopY; 72                 if (button_status != STATUS_ON) { 73                     button_status = STATUS_ON; 74                     Log.e("Slip", "ACTION_UP : STATUS_ON! getFinalLocation"); 75                     if (listener != null) { 76                         listener.slipToTop(); 77                     } 78                 } else { 79                     if (listener != null) { 80                         listener.touchedUp(); 81                     } 82                 } 83                 this.invalidate(); 84  85             } else { 86                 buttonY = buttonBottomY; 87                 if (button_status != STATUS_OFF) { 88                     button_status = STATUS_OFF; 89                     Log.e("Slip", "ACTION_UP : STATUS_OFF! getFinalLocation"); 90                     if (listener != null) { 91                         listener.slipToBottom(); 92                     } 93                 } else { 94                     if (listener != null) { 95                         listener.touchedUp(); 96                     } 97                 } 98                 this.invalidate(); 99             }100 101             break;102         default:103             break;104         }105 106         return true;107     }
View Code

Over,Thanks.

更多相关文章

  1. Android桌面管理
  2. android 自定义progressDialog实现
  3. Android(安卓)弹无虚发之第五弹:来点儿不一样的Toast(自定义Toast,
  4. Android招财进宝手势密码的实现
  5. Android(安卓)自定义view仿IOS开关
  6. 关于安卓活动的生命周期
  7. Android自定义控件之自定义属性
  8. Android中获取手机屏幕各部分的尺寸
  9. Android实现可拖动的悬浮按钮控件

随机推荐

  1. Android的线程使用来更新UI----Thread、H
  2. android ndk在windows下开发环境搭建
  3. Android下的文件访问权限
  4. Android中屏幕相关的操作
  5. 阅读《Android(安卓)从入门到精通》(20)—
  6. android用户界面-布局管理Layout
  7. Android中正确安全删除集合中的元素
  8. android手机开机动画相关代码解析
  9. Android(安卓)代码混淆及第三方jar包不被
  10. 【android 串口开发(二) 之 串口读写操作】