强强学Android_自定义Button

 
                                                                         自定义开关  
 
学习笔记
如有错误之处请大家帮忙指出纠正__谢谢
-------------------------------------------------------------------------------------------------------------------------------------------
 
 
 
 
难点总结 : 一个自定义控件 即实现了触摸事件 又实现了点击事件 , 那么我们就要想到 这两种事件会有冲突 , 每次触摸都会默认的走点击事件
 

1. 自定义类继承View
  
2. 拷贝包含包名的全路径到xml中

3. 界面中找到该控件, 设置初始信息

4. 根据需求绘制界面内容
 
5. 响应用户的触摸事件

6. 创建一个状态更新监听
 
 
 
 
1 ,自定义类继承View
         
    重写构造   复写
 
    加载的图片写到构造方法中 ,通过BitmapFactory . decodeResource(  , )将darwable目录中的图片加载进来得到 BitmapDrawable对象, 调用  
    getBitmap();获得Bitmap对象  
    onMeasure  设置控件大小 setMeasuredDimension( , );参数就写传进来的宽高
    onDraw     将图片画到界面上  canvas . drawBitmap( 加载进来的图片 , x坐标 , y坐标 ,画笔对象  ) 两个坐标默认写 [ 0 ,0 ]
     我们的图片使用代码加载进来的, 代码不运行 , 显示不出来效果,  通过onDraw将图片绘制到界面上
    (因为只有activity 的 onCreat方法中 cetContentView 执行后 系统才会自动调用 我们自定义控件 , )
 
------------------------------------------
 
2, 给图片设置 点击事件 , 当我们点击按钮的时候, 显示状态的切换 , 显示开 , 关 
     
     在构造方法中给自己设置点击侦听,此时,这个组件就可以被点击了 setOnClickListener( this );
     让我们的自定义控件类实现 OnClickListener 接口 , 显示onclick();方法

 

 

     
     这时,组件一旦被点击 , 就会触发给自己设置的点击事件 而触发 onClick( )方法
 
     在onClick中设置 滑块状态位置的改变

     

 1 @Override
 2     public void onClick(View v) {
 3         if(!isDrop){
 4             if(toggleState){
 5                 slideLeft = 0;
 6                 toggleState = false;
 7             }
 8             else{
 9                 slideLeft = slideLeftMax;
10                 toggleState = true;
11             }
12             
13             //让界面无效,onDraw会重新调用
14             invalidate();
15         }
16     }
View Code

 

        
 
------------------------------------------
 
3 ,  添加滑动功能
 
      重写该组件的 onTouchEvent方法,  触摸事件,当这个组件被触摸,就会产生触摸事件
   判断当前的三个状态 , 按下 , 移动 , 抬起 

 

 

 
 
      public   boolean  onTouchEvent(MotionEvent event) { 
   
       return false ; 触摸的时候该方法只会调用一次,再次触摸就不会传给他,而会传给他的父元素 ; 父元素没有指定操作 ,所以第二次触摸不会有任何反应
            return true ; 多少次触摸事件都会传递到我身上 ,我会完全处理,再次点击时,我会继续响应事件
}
 
     int  firstX; 开始坐标

    int lastX; 上一次坐标

    滑块每一动一点 , 都要重新改变起始位置的值 , 否则逻辑不通 
    如果界面滑动滑块没有显示效果 , 一定要考虑是否 重绘界面 ,  invalidate(); 从新调用onDraw方法 否则滑动无效果

    
       
 每滑动一次都要重绘界面 , 从新调用onDraw方法( ), 否则界面无反应 , 我们这里直接将invalidate方法 封装到flushView ( )方法中了
----------------------------------------------

 
  问题1 : 滑块滑出去了 ; 
   
     在调用重绘界面方法的时候 , 进行判断
 
----------------------------------------------
 
 
 4   滑动距离判断滑动是否跳到指定距离 : 
 
  设置 当滑动图片 , 移动小半部分距离的时候 , 松手 --按钮回到起始位置 ; 当移动超过一多半距离的时候 , 松手 直接跳到右边
 
 
 
调用该方法 : 
根据传进来的boolean值 而设置  滑块x轴最大值  和 最小值 , 从而实现跳到 指定位置
 
---------------------------------
 5  开关的点击 和 滑动的冲突 处理 
 
这时, bug就来了 
移动触摸事件生效了 , 点击事件 却失效 了 , 无论如何点击都不会有相应
 
为什么设置了点击监听就 无效了呢
 
原因就是我们的 onTouchEvent 我们将该方法中 super .onTouchEvent(event); 方法删除了
没有调用父类的  onTouchEvent   
强调一个概念, 安卓中其实是只有触摸事件 , 没有点击事件的 , 
 
点击事件是在触摸事件父类中低层自动调用的 , 如果我们不访问父类, 点击事件的onClick ( )方法就不会被调用执行,因此失效
查看低层源码-->   case  MotionEvent. ACTION_UP :中有个  performClick(); -->  li. mOnClickListener .onClick( this );
 
 
父类中已经做了点击事件的调用 ,我们将super 写上 , 点击事件就生效了
-----------------------------------
这样 , 当我们每移动一小部分 , 松手 ,开关状体都会切换 , 不合理 , 需要在点击事件中进行处理 : 
    因为我们移动一丁点的距离 , 松手 , 就会走触摸事件  而 访问父类 导致每次都会调用点击事件 , 这时就需要在点击事件中进行 逻辑判断 
 
当滑块滑动到这里 , 不要让点击事件生效, 让触摸事件去处理相应的逻辑 , 
 
 
 
如果在触摸事件中计算出 用户的操作是滑动, 就使onClick方法中的逻辑不执行就可以了
 
  1.  
     1 public boolean onTouchEvent(MotionEvent event) {
     2         super.onTouchEvent(event);
     3         switch (event.getAction()) { 
     4         case MotionEvent.ACTION_DOWN:
     5             //记录手指按下时的坐标
     6             firstX = lastX = (int) event.getX();
     7 
     8             isDrop = false; //用户一按下就需要从新设置标记状态
     9 
    10             break;
    11         case MotionEvent.ACTION_MOVE:
    12             int newX = (int) event.getX();
    13         
    14             //判断用户当前时点击还是滑动,如果手指移动的像素超过6,那么判定为滑动
    15             if(Math.abs(newX - firstX) > 6){  这里需要取绝对值
    16                 isDrop = true;
    17             }
    18             break;
    19         case MotionEvent.ACTION_UP:
    20             if(isDrop){
    21                      逻辑代码....
    22             }
    23             break;
    24         }
    25         flushView();
    26         return true;
    27     }
    View Code
    public void onClick(View v) {
            if(!isDrop){
                逻辑代码.....
            }
        }
    View Code

     

 
 
 
这时 , 我们的操作都能实现了
 
-----------------------------------------------
 
6 ,自定义属性 
 
  1.  1   res/values/创建attrs.xml
    2 一、在attrs.xml文件中声明属性,如:
    3 <declare-styleable name="MyToggleBtn"> // 声名属性集的名称,即这些属性是属于哪个控件的。 4 <attr name="current_state" format="boolean"/> // 声名属性 current_state 格式为 boolean 类型 5 <attr name="slide_button" format="reference"/> // 声名属性 slide_button 格式为 reference 类型 6 </declare-styleable> 7 所有的format类型,详见注1:
    8 二、在布局文件中使用:在使用之前必须声名命名空间,xmlns:heima="http://schemas.android.com/apk/res/com.itheima.mytogglebtn" 9 说明:xmlns 是XML name space 的缩写; 10 heima 可为任意写符 11 http://schemas.android.com/apk/res/ 此为android固定格式; 12 com.itheima.mytogglebtn 此应用的包名,如manifest配置文件中一致。
    13 布局文件: 14 <com.itheima.mytogglebtn.MyToggleButton 15 xmlns:heima="http://schemas.android.com/apk/res/com.itheima.mytogglebtn" 16 android:layout_width="wrap_content" 17 android:layout_height="wrap_content" 18 heima:slide_button="@drawable/slide_button" />
    19 三、在代码中对属性进行解析,主要代码:
    20 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); // 由attrs 获得 TypeArray
    21 注1: 22 format 常用类型 23 reference 引用 24 color 颜色 25 boolean 布尔值 26 dimension 尺寸值 27 float 浮点值 28 integer 整型值 29 string 字符串 30 enum 枚举值

     

 
---------------------------------------------------------------------------------------------------------------------------------          
自定义Button源代码 : 
  2 
  3 import android.annotation.SuppressLint;
  4 import android.content.Context;
  5 import android.content.res.TypedArray;
  6 import android.graphics.Bitmap;
  7 import android.graphics.BitmapFactory;
  8 import android.graphics.Canvas;
  9 import android.graphics.Paint;
 10 import android.graphics.drawable.BitmapDrawable;
 11 import android.util.AttributeSet;
 12 import android.view.MotionEvent;
 13 import android.view.View;
 14 import android.view.View.OnClickListener;
 15 
 16 
 17 public class MyToggleButton extends View implements OnClickListener {
 18 
 19     private Bitmap slide_button;
 20     private Bitmap switch_background;
 21     int slideLeft = 0;
 22     boolean toggleState = false;
 23     int slideLeftMax;
 24     int firstX;
 25     int lastX;
 26     boolean isDrop = false;
 27     
 28     //AttributeSet:封装了布局文件中定义的属性
 29     public MyToggleButton(Context context, AttributeSet attrs) {
 30         super(context, attrs);
 31         
 32         //获取属性的名字和值
 33 //        int count = attrs.getAttributeCount();
 34 //        for (int i = 0; i < count; i++) {
 35 //            System.out.print(attrs.getAttributeName(i) + ";  ");
 36 //            System.out.println(attrs.getAttributeValue(i));
 37 //        }
 38         
 39         //解析AttributeSet,返回一个TypedArray,此对象中封装了解析出来的属性
 40         //arg1:期望解析出来的属性自定义的属性在R文件中有数组记录 int值
 41         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn);
 42         //取出解析好的属性值
 43         BitmapDrawable bd1 = (BitmapDrawable) ta.getDrawable(R.styleable.MyToggleBtn_switch_bg);
 44         BitmapDrawable bd2 = (BitmapDrawable) ta.getDrawable(R.styleable.MyToggleBtn_slide_bg);
 45         //把BitmapDrawable中封装的图片以Bitmap形式返回
 46         switch_background = bd1.getBitmap();
 47         slide_button = bd2.getBitmap();
 48         
 49 //        slide_button = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
 50 //        switch_background = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
 51     
 52         //算出两个图片的宽度差
 53         slideLeftMax = switch_background.getWidth() - slide_button.getWidth();
 54         //给自己设置点击侦听,此时,这个组件就可以被点击了
 55         setOnClickListener(this);
 56     }
 57 
 58     @Override
 59     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 60 //        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 61         //指定组件宽高
 62         setMeasuredDimension(switch_background.getWidth(), switch_background.getHeight());
 63     }
 64     
 65     @Override
 66     protected void onDraw(Canvas canvas) {
 67         // TODO Auto-generated method stub
 68         super.onDraw(canvas);
 69         
 70         Paint paint = new Paint();
 71         canvas.drawBitmap(switch_background, 0, 0, paint);
 72         canvas.drawBitmap(slide_button, slideLeft, 0, paint);
 73     }
 74 
 75     @Override
 76     public void onClick(View v) {
 77         if(!isDrop){
 78             if(toggleState){
 79                 slideLeft = 0;
 80                 toggleState = false;
 81             }
 82             else{
 83                 slideLeft = slideLeftMax;
 84                 toggleState = true;
 85             }
 86             
 87             //让界面无效,onDraw会重新调用
 88             invalidate();
 89         }
 90     }
 91     
 92     //触摸事件,当这个组件被触摸,就会产生触摸事件 
 93     @Override
 94     public boolean onTouchEvent(MotionEvent event) {
 95         super.onTouchEvent(event);
 96         switch (event.getAction()) { 
 97         case MotionEvent.ACTION_DOWN:
 98             //记录手指按下时的坐标
 99             firstX = lastX = (int) event.getX();
100             isDrop = false;
101             break;
102         case MotionEvent.ACTION_MOVE:
103             int newX = (int) event.getX();
104             //计算手指移动的像素
105             int offsetX = newX - lastX;
106             slideLeft += offsetX;
107             lastX = newX; 
108             
109             //判断用户当前时点击还是滑动,如果手指移动的像素超过6,那么判定为滑动
110             if(Math.abs(newX - firstX) > 6){
111                 isDrop = true;
112             }
113             break;
114         case MotionEvent.ACTION_UP:
115             if(isDrop){
116                 //获取手指抬起时的坐标,判断手指滑动的距离,如果距离大于开关长度的一半,那么就生效
117                 int upX = (int) event.getX();
118                 if(upX - firstX >= slideLeftMax / 2){
119                     toggleState = true;
120                 }
121                 else if(firstX - upX >= slideLeftMax / 2){
122                     toggleState = false;
123                 }
124                 flushState();
125             }
126             break;
127 
128         }
129         flushView();
130         return true;
131     }
132     
133     private void flushState() {
134         if(toggleState)
135             slideLeft = slideLeftMax;
136         else
137             slideLeft = 0;
138 
139     }
140     private void flushView() {
141         if(slideLeft < 0)
142             slideLeft = 0;
143         else if(slideLeft > slideLeftMax)
144             slideLeft = slideLeftMax;
145         invalidate();
146     }
147 }
 1 <RelativeLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.custombutton"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:paddingBottom="@dimen/activity_vertical_margin"
 8     android:paddingLeft="@dimen/activity_horizontal_margin"
 9     android:paddingRight="@dimen/activity_horizontal_margin"
10     android:paddingTop="@dimen/activity_vertical_margin"
11     tools:context=".MainActivity" >
12 
13    
14     <com.itheima.custombutton.MyToggleButton
15         android:layout_width="wrap_content"
16         android:layout_height="wrap_content"
17         android:layout_centerInParent="true"
18         itheima:switch_bg="@drawable/switch_background"
19         itheima:slide_bg="@drawable/slide_button"
20         />
21 
22 </RelativeLayout>
View Code

 

 

1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3     <declare-styleable name="MyToggleBtn">  
4         <attr name="switch_bg" format="reference"/>  
5         <attr name="slide_bg" format="reference"/>  
6     </declare-styleable> 
7 </resources>

 

 

 

转载于:https://www.cnblogs.com/kumar/p/0600a62b2c7e09c8a960e3623d5cc8c6.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值