• 自定义控件(视图)2期笔记09:自定义视图之继承自ViewGroup(仿ViewPager效果案例)


    1. 这里我们继承已有ViewGroup实现自定义控件,模拟出来ViewPager的效果,如下:

    (1)实现的效果图如下:

    (2)实现步骤:

    • 自定义view继承viewGroup

    • 重写onLayout方法,为每一个子View确定位置

    • 重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view

    • 监听UP事件,当手指抬起时候,判断应显示的页面位置,并计算距离、滑动页面。

    • 添加页面切换的监听事件

    2. 具体实现过程,如下:

    (1)新建一个Android工程,命名为"仿ViewPager",如下:

    (2)拷贝(美工设计好的)图片资源文件到res/drawable/,如下:

    (3)代码首先是activity_main.xml,如下:

     1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     xmlns:tools="http://schemas.android.com/tools"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical"
     6     android:gravity="center_horizontal"
     7     tools:context="com.himi.myscrollview.MainActivity" >
     8     
     9     <RadioGroup 
    10         android:id="@+id/radioGroup"
    11         android:orientation="horizontal"
    12         android:layout_width="match_parent"
    13            android:layout_height="wrap_content"
    14         />
    15 
    16     <com.himi.myscrollview.MyScrollView
    17         android:layout_width="match_parent"
    18         android:layout_height="match_parent"
    19         android:id="@+id/myscroll_view" />
    20 
    21 </LinearLayout>

    然后是MyScrollView.java,如下:

      1 package com.himi.myscrollview;
      2 
      3 import android.content.Context;
      4 import android.util.AttributeSet;
      5 import android.view.GestureDetector;
      6 import android.view.GestureDetector.OnGestureListener;
      7 import android.view.MotionEvent;
      8 import android.view.View;
      9 import android.view.ViewGroup;
     10 import android.widget.Scroller;
     11 
     12 public class MyScrollView extends ViewGroup {
     13 
     14     private Context ctx;
     15     /**
     16      * 判断是否发生快速滑动
     17      */
     18     private boolean isFling;
     19     public MyScrollView(Context context, AttributeSet attrs) {
     20         super(context, attrs);
     21         this.ctx = context;
     22         initView();
     23     }
     24 
     25     private void initView() {
     26         //myScroller = new MyScroller(ctx);
     27         myScroller = new Scroller(ctx);
     28         /**
     29          * GestureDetector根据ACTION_DOWN,ACTION_UP,ACTION_MOVE感受不同到手势
     30          * 然后提供一些API接口给程序员进行开发编程
     31          */
     32         detector = new GestureDetector(ctx, new OnGestureListener() {
     33             
     34             /**
     35              * 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发 
     36              * Touch down时触发,不论是touch (包括long) ,scroll
     37              */
     38             private void onDow() {
     39 
     40             }
     41 
     42             /**
     43              * 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发  
     44              */
     45             public boolean onSingleTapUp(MotionEvent e) {
     46                 return false;
     47             }
     48             
     49             /**
     50              * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发  
     51              * 注意和onDown()的区别,强调的是没有松开或者拖动的状态 (单击没有松开或者移动时候就触发此事件,再触发onLongPress事件) 
     52              * 
     53              * onShowPress在Touch了还没有滑动时触发
     54              * onShowPress与onDown,onLongPress比较,onDown只要Touch down一定立刻触发。
     55              */
     56             public void onShowPress(MotionEvent e) {
     57                 
     58             }
     59             
     60             /**
     61              * 响应手指在屏幕上的滑动事件
     62              */
     63             public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
     64                     float distanceY) {
     65                 //移动屏幕
     66                 /**
     67                  * 移动当前的view内容 移动一段距离
     68                  * disX x方向移动的距离  设置为正值(图片向左移动)   为负值(图片向右移动)
     69                  * disY y方向移动的距离
     70                  */
     71                 scrollBy((int)distanceX, 0);
     72                 
     73                 /**
     74                  * 将当前视图的基准点移动到某个点     坐标点原点移动
     75                  * 初始状态基准点是左上角(0,0)
     76                  * x  水平方向x坐标
     77                  * y  竖直方向y坐标
     78                  * 
     79                  * scrollTo(x,y)
     80                  */
     81                 
     82                 return false;
     83             }
     84             
     85             
     86             /**
     87              * 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发 
     88              * 
     89              * Touch了不移动一直Touch down时触发
     90              * Touchdown后过一会没有滑动先触发onShowPress再是onLongPress。
     91              */
     92             public void onLongPress(MotionEvent e) {
     93                 
     94             }
     95             
     96             
     97             /**
     98              * 发生快速滑动的回调方法
     99              * e1:第1个ACTION_DOWN MotionEvent   
    100              * e2:最后一个ACTION_MOVE MotionEvent 
    101              * velocityX:X轴上的移动速度,像素/秒  
    102              * velocityY:Y轴上的移动速度,像素/秒  
    103              */
    104             public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    105                     float velocityY) {
    106                 
    107                 isFling = true;
    108                 if(velocityX>0 && currId>0) {//快速向右滑动
    109                     currId--;
    110                 }else if(velocityX<0 && currId< getChildCount()-1){//快速向右滑动
    111                     currId++;
    112                 }
    113                 moveToDest(currId);
    114                 
    115                 return false;
    116             }
    117             
    118             public boolean onDown(MotionEvent e) {
    119                 return false;
    120             }
    121         });
    122         
    123     }
    124 
    125     
    126     /**
    127      * 计算控件大小
    128      * 做为一个ViewGroup还有一个责任,就是计算每一个子View的大小
    129      * 注意:这里如果不重写这个方法,我们自定义的temp.xml就不会显示布局的内容
    130      * 这里的temp.xml内部也是一个ViewGroup,必须要测量这个大小尺寸,Android系统只有根据得到的尺寸才能安排显示
    131      */
    132     
    133     @Override
    134     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    135         // TODO 自动生成的方法存根
    136         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    137         
    138         int size = MeasureSpec.getSize(widthMeasureSpec);
    139         int mode = MeasureSpec.getMode(widthMeasureSpec);
    140         
    141         for(int i = 0; i<getChildCount(); i++) {
    142             View v = getChildAt(i);
    143             v.measure(widthMeasureSpec, heightMeasureSpec);
    144             
    145             //v.getMeasuredWidth();得到测量的大小
    146         }
    147     }
    148 
    149     
    150     /**
    151      * 确定子view进行布局,确定子view的位置
    152      * 参数changed :判断当前布局是否发生改变(true--改变,  false--没有改变)
    153      * 参数 l	
      是指当前viewgroup(MyScrollView)在其父view中的位置
    154      */
    155     @Override
    156     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    157         for(int i=0; i<getChildCount(); i++) {
    158             //取得小标为i的子view
    159             View view = getChildAt(i); 
    160             
    161             /**
    162              * 父View会根据子View的需求,和自身的情况,来综合确实子View的位置(确定他的大小)
    163              */
    164             //指定子view的位置, 左 , 上, 右, 下 ,是指在viewgroup坐标系中的位置
    165             view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());  
    166             //view.getWidth();//得到View真实的大小
    167         }
    168         
    169     }
    170     /**
    171      * 手势识别的工具类
    172      */
    173     private GestureDetector detector;
    174     
    175     
    176     /**
    177      * 当前的ID值
    178      * 显示在屏幕上的子View的下标
    179      */
    180     private int currId = 0;
    181     
    182     
    183 
    184     /**
    185      *down 事件时的y坐标
    186      */
    187     private int firstY = 0;
    188     /**
    189      * 
    190      *是否中断事件的传递
    191      *返回true的时候中断事件,执行自己的onTouchEvent方法
    192      *返回false的时候,默认处理,不中断,也不会执行自己的onTouchEvent方法
    193      */
    194     @Override
    195     public boolean onInterceptTouchEvent(MotionEvent ev) {
    196         
    197         boolean result = false;
    198          System.out.println("onInterceptTouchEvent::"+ev.getAction());
    199         
    200         switch (ev.getAction()) {
    201         case MotionEvent.ACTION_DOWN:
    202                 firstX = (int) ev.getX();
    203                 firstY= (int) ev.getY();
    204                 break;
    205         case MotionEvent.ACTION_MOVE:
    206                  //手指在屏幕上水平移动的绝对值
    207                  int disx = (int) Math.abs(ev.getX() -firstX);
    208                  
    209                  //手指在屏幕上竖直移动的绝对值
    210                  int disy = (int) Math.abs(ev.getY() -firstY);
    211                  
    212                  if(disx > disy && disx >10) {
    213                      result = true;
    214                  }else {
    215                      result = false;
    216                  }
    217                      
    218                 break;
    219         case MotionEvent.ACTION_UP:
    220             
    221             break;
    222 
    223         default:
    224             break;
    225         }
    226         
    227         return result;
    228     }
    229     
    230     
    231     
    232     
    233     
    234     
    235     /**
    236      * down 事件时的x坐标
    237      */
    238     private int firstX = 0;
    239     
    240     @Override
    241     public boolean onTouchEvent(MotionEvent event) {
    242          super.onTouchEvent(event);
    243          
    244          System.out.println("onTouchEvent::"+event.getAction());
    245          
    246          detector.onTouchEvent(event);
    247          
    248          //添加自己的事件解析
    249          switch (event.getAction()) {
    250         case MotionEvent.ACTION_DOWN:
    251             firstX = (int)event.getX();
    252             break;
    253         case MotionEvent.ACTION_MOVE:
    254             
    255             break;
    256         case MotionEvent.ACTION_UP:
    257             if (!isFling) {//在没有发生快速滑动的时候,才执行按位置判断currId
    258                 int nextId = 0;
    259                 //firstX---ACTION_DOWN的点(x坐标),event.getX()此时是---ACTION_UP的点(x坐标)
    260                 if (event.getX() - firstX > getWidth() / 2) {// 手指向右滑动,超过屏幕的1/2,当前的currId
    261                                                                 // -1
    262                     nextId = currId - 1;
    263                 } else if (firstX - event.getX() > getWidth() / 2) {// 手指向左滑动,超过屏幕的1/2,当前的currId
    264                                                                     // +1
    265                     nextId = currId + 1;
    266                 } else {
    267                     nextId = currId;
    268                 }
    269 
    270                 moveToDest(nextId);
    271             }
    272             isFling = false;//防止快速滑动干扰页面正常翻转
    273             //scrollTo(0,0);
    274             break;
    275         }
    276          return true;
    277     }
    278     
    279     /**
    280      * 计算位移的工具类
    281      */
    282     //private MyScroller myScroller;//自定义的Scroller
    283     private Scroller myScroller;//调用系统的Scroller(更加强大),注意修改上面的构造方法
    284     /**
    285      * 移动到指定的屏幕上
    286      * @param nextId  屏幕的下标
    287      */
    288     public void moveToDest(int nextId) {
    289         /**
    290          * 首先对nextId进行判断,确保是在合理的范围
    291          * 即 nextId >=0 && nextId <=getChildCount()-1
    292          */
    293         //确保currId >=0
    294         currId = (nextId >=0)?nextId:0;
    295         
    296         //确保currId <=getChildCount()-1
    297         currId = (nextId <=getChildCount()-1)?nextId:(getChildCount()-1);
    298         
    299         //用户体验是立即跳转的下一个页面,太过迅速,不自然,用户体验不好。要修改,改为稍缓移动到下一个页面
    300         //瞬间移动
    301         //scrollTo(currId*getWidth(),0);
    302         
    303         
    304         //触发MyPageChangedListener事件
    305         if(pageChangedListener != null) {
    306             pageChangedListener.moveToDest(currId);
    307         }
    308         
    309         //最终的位置 - 现在的位置 = 要移动的距离
    310         int distance = currId*getWidth()-getScrollX();
    311         
    312         
    313         //myScroller.startScroll(getScrollX(), 0, distance,0);//自定义的
    314        /**
    315         * 记录下开始时候:
    316         * startScroll方法参数,如下:
    317         * 参数1:x坐标 
    318         * 参数2:y坐标 
    319         * 参数3: x方向移动距离  
    320         * 参数4:y方向移动的距离 
    321         * 参数5: 设置运行的时间
    322         */
    323         myScroller.startScroll(getScrollX(), 0, distance,0,Math.abs(distance));
    324         
    325         /**
    326          * invalidate()会刷新当前View,会导致onDraw方法的执行
    327          */
    328         invalidate();
    329     }
    330     
    331     /**
    332      * invalidate()会导致computeScroll()方法的执行
    333      */
    334     
    335     @Override
    336     public void computeScroll() {
    337         if(myScroller.computeScrollOffset()) {//移动图片页面还在进行中,没有结束
    338             int newX = (int) myScroller.getCurrX();
    339             System.out.println("newX::"+newX);
    340             scrollTo(newX, 0);
    341             
    342             //前面设置改变了View视图,但是必须刷新才能显示
    343             //invalidate()方法调用,又会启用computeScroll()方法,如此反复,直至View不再移动(View移动到目的位置,终止移动)
    344             invalidate();
    345         }
    346     }
    347     
    348     
    349     public MyPageChangedListener getPageChangedListener() {
    350         return pageChangedListener;
    351     }
    352 
    353     public void setPageChangedListener(MyPageChangedListener pageChangedListener) {
    354         this.pageChangedListener = pageChangedListener;
    355     }
    356     private MyPageChangedListener pageChangedListener;
    357     
    358     
    359     /**
    360      * 页面改变时候的监听接口
    361      */
    362     
    363     public interface MyPageChangedListener {
    364         void moveToDest(int currid);
    365     }
    366 }

    还有就是计算位移距离的工具类MyScroller,如下:

      1 package com.himi.myscrollview;
      2 
      3 import android.content.Context;
      4 import android.os.SystemClock;
      5 
      6 /**
      7  * 计算位移距离的工具类
      8  * @author Administrator
      9  *
     10  */
     11 
     12 public class MyScroller {
     13     private int startX;
     14     private int startY;
     15     private int disX;
     16     private int disY;
     17     
     18     /**
     19      * 开始执行动画的时间
     20      */
     21     private long startTime;
     22     /**
     23      * 判断是否正在执行动画
     24      * true 还在运行
     25      * false 已经停止
     26      */
     27     private boolean isFinish;
     28 
     29     public MyScroller(Context context) {
     30         
     31     }
     32 
     33     /**
     34      * 开始移动
     35      * @param startX  开始时的X坐标
     36      * @param startY  开始时的Y坐标
     37      * @param disX    X方向要移动的距离
     38      * @param disY    Y方向要移动的距离
     39      */
     40     public void startScroll(int startX, int startY, int disX, int disY) {
     41             this.startX = startX;
     42             this.startY = startY;
     43             this.disX = disX;
     44             this.disY = disY;
     45             this.startTime = SystemClock.uptimeMillis();//手机开机时开始计算的毫秒值
     46             
     47             this.isFinish = false;
     48         
     49     }
     50     /**
     51      * 默认运行的时间
     52      * 毫秒值
     53      */
     54     private int duration = 500;
     55     /**
     56      * 当前的X的值
     57      */
     58     
     59     private long currX;
     60     /**
     61      * 当前的Y的值
     62      */
     63     private long currY;
     64 
     65     public long getCurrX() {
     66         return currX;
     67     }
     68 
     69     public void setCurrX(long currX) {
     70         this.currX = currX;
     71     }
     72 
     73     /**
     74      * 计算一下当前的运行状况
     75      *返回值 :
     76      * true 还在运行
     77      * false 运行结束
     78      */
     79     public boolean computeScrollOffset() {
     80         if(isFinish) {
     81             return false;
     82         }
     83         
     84         //获得所用的时间
     85         long passTime = SystemClock.uptimeMillis() - startTime;
     86         
     87         //如果时间还在允许的范围内
     88         if(passTime<duration) {
     89             //当前的位置 = 开始的位置 +移动的距离 (距离 = 速度*时间)
     90             currX = startX+disX*passTime/duration;
     91             currY= startY+disY*passTime/duration;
     92         }else  {
     93             currX = startX + disX;
     94             currY = startY + disY;
     95             isFinish = true;
     96         }
     97         
     98         
     99         return true;
    100     }
    101 
    102 }

    其中测试布局xml文件temp.xml,如下:

     1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     xmlns:tools="http://schemas.android.com/tools"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:gravity="center_horizontal"
     6     android:background="@android:color/darker_gray"
     7     android:orientation="vertical"
     8     tools:context="com.himi.myscrollview.MainActivity" >
     9 
    10     <Button
    11         android:id="@+id/button1"
    12         android:layout_width="wrap_content"
    13         android:layout_height="wrap_content"
    14         android:text="Button" />
    15 
    16     <TextView
    17         android:id="@+id/textView1"
    18         android:layout_width="wrap_content"
    19         android:layout_height="wrap_content"
    20         android:text="Large Text"
    21         android:textAppearance="?android:attr/textAppearanceLarge" />
    22 
    23     <ScrollView
    24         android:id="@+id/scrollView1"
    25         android:layout_width="match_parent"
    26         android:layout_height="wrap_content" >
    27 
    28         <LinearLayout
    29             android:layout_width="match_parent"
    30             android:layout_height="match_parent"
    31             android:orientation="vertical" >
    32 
    33             <TextView
    34                 android:id="@+id/textView0"
    35                 android:layout_width="wrap_content"
    36                 android:layout_height="wrap_content"
    37                 android:text="Large Text"
    38                 android:textAppearance="?android:attr/textAppearanceLarge" />
    39 
    40             <TextView
    41                 android:id="@+id/textView2"
    42                 android:layout_width="wrap_content"
    43                 android:layout_height="wrap_content"
    44                 android:text="Large Text"
    45                 android:textAppearance="?android:attr/textAppearanceLarge" />
    46 
    47             <TextView
    48                 android:id="@+id/textView3"
    49                 android:layout_width="wrap_content"
    50                 android:layout_height="wrap_content"
    51                 android:text="Large Text"
    52                 android:textAppearance="?android:attr/textAppearanceLarge" />
    53 
    54             <TextView
    55                 android:id="@+id/textView4"
    56                 android:layout_width="wrap_content"
    57                 android:layout_height="wrap_content"
    58                 android:text="Large Text"
    59                 android:textAppearance="?android:attr/textAppearanceLarge" />
    60 
    61             <TextView
    62                 android:id="@+id/textView5"
    63                 android:layout_width="wrap_content"
    64                 android:layout_height="wrap_content"
    65                 android:text="Large Text"
    66                 android:textAppearance="?android:attr/textAppearanceLarge" />
    67 
    68             <TextView
    69                 android:id="@+id/textView6"
    70                 android:layout_width="wrap_content"
    71                 android:layout_height="wrap_content"
    72                 android:text="Large Text"
    73                 android:textAppearance="?android:attr/textAppearanceLarge" />
    74         </LinearLayout>
    75     </ScrollView>
    76 
    77 </LinearLayout>

    这个temp.xml布局效果如下:

    (4)MainActivity,如下:

     1 package com.himi.myscrollview;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.view.View;
     6 import android.widget.ImageView;
     7 import android.widget.RadioButton;
     8 import android.widget.RadioGroup;
     9 import android.widget.RadioGroup.OnCheckedChangeListener;
    10 
    11 import com.himi.myscrollview.MyScrollView.MyPageChangedListener;
    12 
    13 public class MainActivity extends Activity {
    14     private MyScrollView msv;
    15     //图片的资源ID数组
    16     private int[] ids = new int[] { R.drawable.a1,R.drawable.a2,R.drawable.a3,
    17             R.drawable.a4,R.drawable.a5,R.drawable.a6
    18     };
    19     
    20     private RadioGroup radioGroup;
    21     @Override
    22     protected void onCreate(Bundle savedInstanceState) {
    23         super.onCreate(savedInstanceState);
    24         setContentView(R.layout.activity_main);
    25         
    26         msv =  (MyScrollView) findViewById(R.id.myscroll_view);
    27         radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
    28         
    29         for(int i=0; i<ids.length; i++) {
    30             ImageView image = new ImageView(this);
    31             image.setBackgroundResource(ids[i]);
    32             //添加image图片资源  到  自定义的MyScrollView(ViewGroup是父容器)
    33             msv.addView(image);
    34         
    35         }
    36         
    37         msv.setPageChangedListener(new MyPageChangedListener() {
    38             
    39             public void moveToDest(int currid) {
    40                 ((RadioButton)radioGroup.getChildAt(currid)).setChecked(true);
    41                 
    42             }
    43         });
    44         
    45         radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    46             
    47             public void onCheckedChanged(RadioGroup group, int checkedId) {
    48                 msv.moveToDest(checkedId);
    49                 
    50             }
    51         });
    52         
    53         //给自定义的viewGroup添加测试的布局
    54         View temp  = getLayoutInflater().inflate(R.layout.temp, null);
    55         msv.addView(temp,2);
    56         
    57         for (int i = 0; i < msv.getChildCount(); i++) {
    58 
    59             // 添加radioButton
    60             RadioButton rbtn = new RadioButton(this);
    61             rbtn.setId(i);
    62             radioGroup.addView(rbtn);
    63             if (i == 0) {
    64                 rbtn.setChecked(true);
    65             }
    66         }
    67     }
    68 
    69 }

    2. 附件理解图:

    (1)起始状态,多张页面pager位置布局图,在上面的Layout方法中,如下:

    (2)当我们手指水平向右滑动,页面标号id减少

                     手指水平向左滑动,页面标号id增加

  • 相关阅读:
    整理用js实现tab标签页
    整理悬浮在列表中a元素时改变a元素上下边框颜色的问题。
    整理Javascript基础数据和引用数据复制值的问题
    这一周的学习整理
    关于变量作用域的一点整理
    Javascript 知识遗漏点梳理。
    安卓自定义TextView实现自动滚动
    解决 IntelliJ IDEA Tomcat 控制台中文输出乱码问题
    Tomcat 控制台UTF-8乱码问题
    java 实现hex文件转换bin保存至内存中
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4849658.html
Copyright © 2020-2023  润新知