• 程序媛也话Android 之 自定义控件(垂直方向滑动条)


    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.OnClickListener
    12         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.

  • 相关阅读:
    java ArrayList存储基本类型
    java ArrayList的几种方法使用
    java ArrayList的基本使用
    java 猜数字
    java Random随机生成一个数
    java Scanner输入数字、字符串
    java 构造方法
    java this的应用
    java pravite关键字的使用
    云计算服务
  • 原文地址:https://www.cnblogs.com/zhangxinyan/p/3513723.html
Copyright © 2020-2023  润新知