Android-自定义View实现轮播图

主要步骤
1.自定义View的实现过程(测量、布局、绘制)
2.事件拦截机制方法、图片轮播时的Scroller对象使用
3.轮播图自动轮播时,Timer、TimerTask、Handler三者的结合
4.自定义l轮播图底部圆点布局实现
主要思路
  • 1.我们需要自定义一个继承自FrameLayout的布局,利用FrameLayout布-局的特性(在同一位置放置不同的view最终显示的是最后一个View),我们就可以实现底部圆点的布局。
  • 2.我们需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示
  • 3.我们就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们需要的布局LinearLayout来实现。
1.自定义View的实现过程(测量、布局、绘制)

测量:要实现的是一个viewGroup的容器,那么 我们就应该需要知道该容器中的子视图 ,我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图*的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少 。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    /**
     * 由于我们要实现的是一个viewGroup的容器,那么
     * 我们就应该需要知道该容器中的子视图
     * 我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图
     * 的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少
     */
    //1.求出子视图的个数
     children = getChildCount(); //我们就可以知道子视图的个数
    if(0 == children) {
        setMeasuredDimension(0, 0);
    } else {
        //2.测量子视图的宽度和高度
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //此时我们一第一个子视图为基础,也就是说我们的ViewGroup的高度就是我们第一个子视图的高度
        //宽度就是我们第一个子视图的宽度*子视图的个数
        View view = getChildAt(0); //因为此时第一个视图绝对是存在的
        
        //3.根据子视图的宽度和高度,来求出该ViewGroup的宽度和高度
        childheight = view.getMeasuredHeight();
        childwidth =  view.getMeasuredWidth();
        int childwidth = view.getMeasuredWidth() * children; //宽度是我们所有字视图的宽度的总和
        setMeasuredDimension(childwidth, childheight);
    }
}

布局:继承ViewGroup必须要实现布局onLayout方法。其中change 代表的时候是当我们的ViewGroup布局位置发生改变的为true 没有发生改变为false

protected void onLayout(boolean change, int l, int t, int r, int b) {
       if(change) {
           int leftMargin = 0;
           for(int i = 0; i < children; i++) {
               View view = getChildAt(i); //拿出每个视图
               view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
               leftMargin +=childwidth;
        }
    }
}

绘制:对于绘制来说,因为我们是自定义的ViewGroup容器 针对于容器的绘制,其实就是容器内的子控件的绘制过程,那么我们只需要 调用系统自带的绘制即可 也就说,对于viewGroup绘制过程不需要再重写该方法调用系统自带即可。

2.事件拦截机制方法、图片轮播时的Scroller对象使用

事件拦截机制方法:调用容器的拦截方法onInterceptTouchEvent。针对于该方法我们可以理解为 如果说该方法的返回值为trued的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件。 如果说 返回值为false的时候,那么我们自定义的ViewGroup 容器将不会接受此次事件的处理过程,将会继续向下传递该事件, 针对于我们自定义的ViewGroup 我们当然是希望我们的ViewGroup 容器处理接受事件 那么我们的返回值就是true。 如果返回true的话 真正处理该事件的方法 是onTouch方法。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return true;
}

事件处理过程:用2中方式 来实现 轮播图的手动 轮播

  • 1.利用scrollTo scrollBy完成轮播图的手动轮播

  • 2.利用Scroller 对象 完成轮播图的手动轮播
    事件处理思路:

  • 第一:我们在滑动屏幕图片的过程中,其实就是我们自定义ViewGroup 的子视图的移动过程,那么我们只需要知道滑动之前的横坐标和滑动之后的横坐标,此时 我们就可以此过程中移动的距离,我们在利用scrollBy方法实现图片的滑动 所以 此时我们需要2个值 是需要我们 求出的: 移动之前,移动之后的 横坐标值。

  • 第二:当我们第一次 按下得那一瞬间, 此时的移动之前和移动之后的值是相等的,也就是我们此时按下那一瞬间得哪一个点的横坐标的值。

  • 第三:我们在不断的滑动过程中,是会不断地调用我们ACTION_MOVE方法,那么此时我们就应该将 移动之前的值 和移动之后的进行保存。 此时我们能够算出滑动的距离

  • 第四: 在我们抬起那一瞬间 我们需要计算出我们此时将要滑动到那张图片的位置上。 我们此时就需要求得出将要滑动到的那张图片的索引值,(我们当前ViewGroup的滑动位置 +我们的每一张图片的宽度/2)/我们的每一张图片的宽度
    此时我们就可以利用scrollTo方法滑动到该图片的位置上。

    public boolean onTouchEvent(MotionEvent event) {
      switch(event.getAction()) {
      case MotionEvent.ACTION_DOWN:
          stopAuto();
          if(!scroller.isFinished()) {
              scroller.abortAnimation();
          }
          isClick = true;
          x = (int) event.getX();
          break;
      case MotionEvent.ACTION_MOVE:
          int moveX = (int) event.getX();
          int distance = moveX - x;
          scrollBy(-distance, 0);
          x = moveX;
          isClick = false;
          break;
      case MotionEvent.ACTION_UP:
          int scrollX = getScrollX(); //当前ViewGroup的滑动位置 
          index = (scrollX + childwidth/2)/childwidth;
          
          if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
              index = 0;
          } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
              index = children -1;
          }
          
          if(isClick) {
              listener.clickImageIndex(index);
          } else {
              int dx = index * childwidth - scrollX; //当前滑动的距离
              //scrollTo(index*childwidth, 0);
              scroller.startScroll(scrollX, 0, dx, 0);
              postInvalidate(); 
              barnnerViewGroupListener.selectImage(index);
          }
          startAuto();
          
          break;
      default:
          break;
      }
      return true; //返回true 的目的是告诉
    

图片轮播时的Scroller对象使用:在computeScroll()方面里面,在滑动结束的时候移动到当前滑动抬起位置。
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()) { //当前scroller对象是否已经滑动完毕
scrollTo(scroller.getCurrX(), 0);
invalidate();
}
}
并且在MotionEvent.ACTION_UP实现滑动:
case MotionEvent.ACTION_UP:
int scrollX = getScrollX(); //当前ViewGroup的滑动位置
index = (scrollX + childwidth/2)/childwidth;

        if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
            index = 0;
        } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
            index = children -1;
        }
        
        if(isClick) {
            listener.clickImageIndex(index);
        } else {
            int dx = index * childwidth - scrollX; //当前滑动的距离
            //scrollTo(index*childwidth, 0);
            scroller.startScroll(scrollX, 0, dx, 0);
            postInvalidate(); 
            barnnerViewGroupListener.selectImage(index);
        }
        startAuto();
        break;
3.轮播图自动轮播时,Timer、TimerTask、Handler三者的结合

采用Timer,TimerTask,Handler三者结合的方式来实现自动轮播我们会抽出2个方法来控制,是否启动自动轮播,我们称之为startAuto(),stopAuto();在2个方法控制过程中,实际上希望控制的是自动开启轮播图的开关。那么就需要一个变量参数 来作为自动开启轮播图的开关,我们称之为isAuto boolean true代表开启 false代表关闭。
private boolean isAuto = true; //默认情况下我们是开启自动轮播
private Timer timer = new Timer();
private TimerTask task;
private Handler autoHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch(msg.what) {
case 0: //此时我们需要图片的自动轮播
if(++index >= children) { //说明我们 此时如果是最后一张图片的话,那么我们将会从第一章图片开始重新滑动
index = 0;
}

            scrollTo(childwidth * index, 0);
            barnnerViewGroupListener.selectImage(index);
            break;
        }
        
    };
  };

  private void startAuto() {
      isAuto = true;
   }

  private void stopAuto() {
      isAuto = false;
  }

  private void initobj() {
      scroller = new Scroller(getContext());
    
      task = new TimerTask() {
        public void run() {
            if(isAuto) { //开启轮播图
                autoHandler.sendEmptyMessage(0);
            }
        }
    };
    
    timer.schedule(task, 100,1000);
  }

实现图片轮播图的核心的ImageBarnnerViewGroup类:
public class ImageBarnnerViewGroup extends ViewGroup{
private int children; //我们viewGroup子视图的总个数
private int childheight; //子视图的高度
private int childwidth; //子视图的宽度

      private int x; //此时的X的值,代表的是第一次按下的位置横坐标 ,每一次移动过程中 移动之前的位置横坐标
      private int index; //代表的是我们每一张图片的索引

      private Scroller scroller;

        /**
         * 要想实现图片的单机事件的获取
         * 我们采用的方法 就是利用一个单击变量开关进行判断 , 再用户离开屏幕的一瞬间我们去判断变量开关来判断用户的操作是点击 还 是移动
         */
      private boolean isClick; //true 点击事件  false 移动
      private ImageBarnnerListener listener;

      public ImageBarnnerListener getListener() {
           return listener;
      }

      public void setListener(ImageBarnnerListener listener) {
           this.listener = listener;
      }

      public interface ImageBarnnerListener {
           void clickImageIndex(int pos); //pos代表的是我们当前图片的具体索引值
      }

      private ImageBarnnerViewGroupListener barnnerViewGroupListener;

      public ImageBarnnerViewGroupListener getBarnnerViewGroupListener() {
          return barnnerViewGroupListener;
      }

     public void setBarnnerViewGroupListener(
          ImageBarnnerViewGroupListener barnnerViewGroupListener) {
          this.barnnerViewGroupListener = barnnerViewGroupListener;
     }

     /**
      * 要想实现图片轮播底部圆点以及底部圆点切换功能步骤思路:
      * 1.我们需要自定义一个继承自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的view最终显示的是最后一个View)
      * 我们就可以实现底部圆点的布局。
      * 2.我们需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示
      * 3.我们就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们
      * 需要的布局LinearLayout来实现。
      */
     //===自动轮播
     /**
      * 我会采用Timer,TimerTask,Handler三者结合的方式来实现自动轮播
      * 我们会抽出2个方法来控制,是否启动自动轮播,我们称之为startAuto(),stopAuto();
      * 那么我们在2个方法控制过程中,我们实际上希望控制的是自动开启轮播图的开关
      * 那么我们就需要一个变量参数 来作为我们自动开启轮播图的开关,我们称之为isAuto boolean true代表开启 false代表关闭
      * @param context
      */
     private boolean isAuto = true; //默认情况下我们是开启自动轮播
     private Timer timer = new Timer();
     private TimerTask task;
     private Handler autoHandler = new Handler() {
         public void handleMessage(android.os.Message msg) {
             switch(msg.what) {
             case 0: //此时我们需要图片的自动轮播
                 if(++index >= children) { //说明我们 此时如果是最后一张图片的话,那么我们将会从第一章图片开始重新滑动
                     index = 0;
                 }
            
                 scrollTo(childwidth * index, 0);
                 barnnerViewGroupListener.selectImage(index);
                 break;
             }
        
         };
     };

     private void startAuto() {
          isAuto = true;
     }

     private void stopAuto() {
         isAuto = false;
     }
     public ImageBarnnerViewGroup(Context context) {
         super(context);
         initobj();
     }

     public ImageBarnnerViewGroup(Context context, AttributeSet attrs) {
         super(context, attrs);
         initobj();
     }

     public ImageBarnnerViewGroup(Context context, AttributeSet attrs,
             int defStyle) {
         super(context, attrs, defStyle);
         initobj();
     }

     private void initobj() {
         scroller = new Scroller(getContext());
    
         task = new TimerTask() {
             public void run() {
                 if(isAuto) { //开启轮播图
                autoHandler.sendEmptyMessage(0);
                 }
             }
         };
    
         timer.schedule(task, 100,1000);
     }

     @Override
     public void computeScroll() {
         super.computeScroll();
         if(scroller.computeScrollOffset()) { //当前scroller对象是否已经滑动完毕
             scrollTo(scroller.getCurrX(), 0);
             invalidate();
         }
     }

     /**
      * 我们在自定义ViewGroup中,我们必须要实现的方法有:测量-》布局 -》绘制
      * 那么对于测量来说就是:onMeasure()
      * 我们对于绘制来说,因为我们是自定义的ViewGroup容器 针对于容器的绘制
      * 其实就是容器内的子控件的绘制过程,那么我们只需要 调用系统自带的绘制即可 也就说,对于viewGroup绘制过程不需要再重写该方法
      * 调用系统自带即可。
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         /**
          * 由于我们要实现的是一个viewGroup的容器,那么
          * 我们就应该需要知道该容器中的子视图
          * 我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图
          * 的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少
          */
         //1.求出子视图的个数
          children = getChildCount(); //我们就可以知道子视图的个数
         if(0 == children) {
             setMeasuredDimension(0, 0);
         } else {
             //2.测量子视图的宽度和高度
             measureChildren(widthMeasureSpec, heightMeasureSpec);
             //此时我们一第一个子视图为基础,也就是说我们的ViewGroup的高度就是我们第一个子视图的高度
             //宽度就是我们第一个子视图的宽度*子视图的个数
             View view = getChildAt(0); //因为此时第一个视图绝对是存在的
        
             //3.根据子视图的宽度和高度,来求出该ViewGroup的宽度和高度
             childheight = view.getMeasuredHeight();
             childwidth =  view.getMeasuredWidth();
             int childwidth = view.getMeasuredWidth() * children; //宽度是我们所有字视图的宽度的总和
             setMeasuredDimension(childwidth, childheight);
         }
     }

     /**
           * 事件的传递过程中调用方法,我们需要调用容器的拦截方法 onInterceptTouchEvent
      * 针对于该方法我们可以理解为 如果说该方法的返回值为trued的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件
      * 如果说 返回值为false的时候,那么我们自定义的ViewGroup 容器将不会接受此次事件的处理过程,将会继续向下传递该事件, 
      * 针对于我们自定义的ViewGroup 我们当然是希望我们的ViewGroup 容器处理接受事件 那么我们的返回值就是true
      * 如果返回true的话  真正处理该事件的方法 是onTouch方法
      */
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
           return true;
    }
    /**
      * 用2中方式 来实现 轮播图的手动 轮播
      * 1.利用scrollTo scrollBy完成轮播图的手动轮播
      * 2.利用Scroller 对象 完成轮播图的手动轮播
      * 
      * 第一:我们在滑动屏幕图片的过程中,其实就是我们自定义ViewGroup 的子视图的移动过程,那么我们只需要知道
      * 滑动之前的横坐标和滑动之后的横坐标,此时 我们就可以此过程中移动的距离,我们在利用scrollBy方法实现图片的滑动
      * 所以 此时我们需要2个值 是需要我们 求出的: 移动之前,移动之后的 横坐标值
      * 
      * 第二:当我们第一次 按下得那一瞬间, 此时的移动之前和移动之后的值是相等的,也就是我们此时按下那一瞬间得哪一个点的横坐标的值。
      * 
      * 第三:我们在不断的滑动过程中,是会不断地调用我们ACTION_MOVE方法,那么此时我们就应该将 移动之前的值 和移动之后的进行保存。
      * 此时我们能够算出滑动的距离
      * 
      * 第四: 在我们抬起那一瞬间 我们需要计算出我们此时将要滑动到那张图片的位置上。
      * 
      * 我们此时就需要求得出将要滑动到的那张图片的索引值,
      * (我们当前ViewGroup的滑动位置 +我们的每一张图片的宽度/2)/我们的每一张图片的宽度值
      * 
      * 此时我们就可以利用scrollTo方法滑动到该图片的位置上。
      */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch(event.getAction()) {
         case MotionEvent.ACTION_DOWN:
             stopAuto();
             if(!scroller.isFinished()) {
                 scroller.abortAnimation();
             }
             isClick = true;
             x = (int) event.getX();
             break;
         case MotionEvent.ACTION_MOVE:
             int moveX = (int) event.getX();
             int distance = moveX - x;
             scrollBy(-distance, 0);
             x = moveX;
             isClick = false;
             break;
         case MotionEvent.ACTION_UP:
             int scrollX = getScrollX(); //当前ViewGroup的滑动位置 
             index = (scrollX + childwidth/2)/childwidth;
        
             if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
                 index = 0;
             } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
                 index = children -1;
             }
        
             if(isClick) {
                 listener.clickImageIndex(index);
             } else {
                 int dx = index * childwidth - scrollX; //当前滑动的距离
                 //scrollTo(index*childwidth, 0);
                 scroller.startScroll(scrollX, 0, dx, 0);
                 postInvalidate(); 
                 barnnerViewGroupListener.selectImage(index);
             }
             startAuto();
        
             break;
         default:
             break;
         }
         return true; //返回true 的目的是告诉
     }

     /**
      * 继承ViewGroup必须要实现布局onLayout方法
      * @param change 代表的时候是当我们的ViewGroup布局位置发生改变的为true 没有发生改变为false
      */
     @Override
     protected void onLayout(boolean change, int l, int t, int r, int b) {
         if(change) {
             int leftMargin = 0;
             for(int i = 0; i < children; i++) {
                 View view = getChildAt(i); //拿出每个视图
                 view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
                 leftMargin +=childwidth;
             }
         }
     }

     public interface ImageBarnnerViewGroupListener {
         void selectImage(int index);
     }

    }
4.自定义l轮播图底部圆点布局实现

4.1实现图片轮播底部圆点以及底部圆点切换功能步骤思路:

  • 1.需要自定义一个继承自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的view最终显示的是最后一个View) 就可以实现底部圆点的布局。

  • 2.需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示

  • 3.就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们 需要的布局LinearLayout来实现。
    public class ImageBarnnerFramLayout extends FrameLayout implements ImageBarnnerViewGroupListener,ImageBarnnerListener {
    private ImageBarnnerViewGroup mImageBarnnerViewGroup;
    private LinearLayout linearLayout;
    private Context mContext;
    public ImageBarnnerFramLayout(Context context) {
    super(context);
    mContext =context;
    initImageBarnnerViewGroup();
    initDoLinearLayout();
    }

    public ImageBarnnerFramLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext =context;
        initImageBarnnerViewGroup();
        initDoLinearLayout();
    }
    
    public ImageBarnnerFramLayout(Context context, AttributeSet attrs,
          int defStyle) {
         super(context, attrs, defStyle);
         mContext =context;
         initImageBarnnerViewGroup();
         initDoLinearLayout();
    }
    
    /**
     * 初始化 我们的底部圆点布局
     */
    private void initDoLinearLayout() {
       linearLayout = new LinearLayout(getContext());
       FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 40);
       linearLayout.setLayoutParams(lp);
       linearLayout.setOrientation(LinearLayout.HORIZONTAL);
       linearLayout.setGravity(Gravity.CENTER);
       
       linearLayout.setBackgroundColor(Color.RED);
       
       addView(linearLayout);
        
       FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
       layoutParams.gravity = Gravity.BOTTOM;
       
       linearLayout.setLayoutParams(layoutParams);
       
       //这里有一个知识点,就是3.0以后 我们使用的是setAlpha() 在3.0之前我们使用的是setAlpha(),但是调用者不同
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
           linearLayout.setAlpha(0.5f);
       } else {
           linearLayout.getBackground().setAlpha(100);
       }
    }
    

    /**
    * 初始化 我们的自定义的图片轮播功能核心类
    */
    private void initImageBarnnerViewGroup() {
    mImageBarnnerViewGroup = new ImageBarnnerViewGroup(getContext());
    FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT);
    mImageBarnnerViewGroup.setLayoutParams(lp);
    mImageBarnnerViewGroup.setListener(this);
    mImageBarnnerViewGroup.setBarnnerViewGroupListener(this);
    addView(mImageBarnnerViewGroup);

    }
    
    public void addBitmaps(List list) {
        for(int i =0; i

具体Drawable没有选中底图效果:

  
  
        
        

具体Drawable选中底图效果:

  

     

     

  

效果图如下:

Android-自定义View实现轮播图_第1张图片
1.png

Android-自定义View实现轮播图_第2张图片
2.png

你可能感兴趣的:(Android-自定义View实现轮播图)