>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19708 个阅读者 刷新本主题
 * 贴子主题:  自定义ViewGroup和FrameLayout实现轮播图(包括底部小圆点) 回复文章 点赞(0)  收藏  
作者:mary    发表时间:2020-03-08 14:59:10     消息  查看  搜索  好友  邮件  复制  引用

                                                                                                

自定义ViewGroup和FrameLayout实现轮播图(包括底部小圆点)

广告轮播图在现在的APP首页比较常见,主要的实现方式有两种,一种是通过ViewPager,一种是通过自定义ViewGroup。前者的实现方式比较简便,本篇文章讲的是第二种方法,有人说用ViewPager不是更方便吗,的确,但是我们通过自己定义ViewGroup,可以更深入了解ViewGroup内部的原理。用别人造的轮子确实方便,但有的时候拆开轮子看看,我们也许会学到更多。

         效果图

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

         主要的思路如下:

首先,轮播图可以理解为n张图片横向相连,并通过一个单张图片大小的的相框,一次移动一张图片的距离,我们可以通过一个ViewGroup容器来存放这n张图片,然后用Scroller类配合TimeTask和Handler来实现图片的滑动,至于小圆点,也就是一个横向存放圆点图片的LinearLayout布局,可以再继承一个FrameLayout类,把存放图片的ViewGroup和存放圆点的LinearLayout都放进去,并根据图片滑动的位置来设置圆点的切换。

         这就需要对ViewGroup的测量过程(onMeasure),布局过程 (onLayout)和绘制过程(onDraw)以及onTouch点击事件的处理有所了解。

         直接看代码

ViewGroup类:        

   package com.imagebanner;

import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

import java.util.Timer;
import java.util.TimerTask;

     /**
* 该类是实现图片轮播核心类
*/

public   class  ImageBannerViewGroup  extends ViewGroup {

     //子视图个数
     private  int children ;

     //子视图宽度和高度
     private  int childWidth ;
     private  int childHeight ;

     private  int x ;
     private  int index =  0 ;

     private Scroller scroller ;

     //图片点击事件的监听器
     private ImageBannerListner listner ;

     //底部圆点切换的监听器
     private ImageBannerViewGroupListener bannerViewGroupListener ;

     public ImageBannerViewGroupListener  getBannerViewGroupListener() {
         return bannerViewGroupListener;
    }

     public  void  setBannerViewGroupListener(ImageBannerViewGroupListener bannerViewGroupListener) {
         this.bannerViewGroupListener = bannerViewGroupListener;
    }

     //判断是点击事件还是移动事件的标识
     private  boolean isClick ;

     public ImageBannerListner  getListner() {
         return listner;
    }

     public  void  setListner(ImageBannerListner listner) {
         this.listner = listner;
    }

     public   interface  ImageBannerListner{
         void clickImageIndex(  int pos );
    }

     //判断是否自动轮播的标识
     private  boolean isAuto =  true ;

     //自动轮播
     private Timer timer =  new Timer();
     private TimerTask task ;
     private Handler autoHandler =  new Handler(){

         @Override
         public  void  handleMessage(Message msg) {
             switch ( msg.what ){
                 case  0:

                     //最后一张的时候返回第一张
                     if( ++index >= children ){
                        index =  0 ;
                    }

                    scrollTo( childWidth * index ,  0 );
                     //图片切换完毕后通知FrameLayout切换底部圆点
                    bannerViewGroupListener.selectImage(index);
                     break;
            }
        }
    };

     private  void  startAuto(){
        isAuto =  true ;
    }

     private  void  stopAuto(){
        isAuto =  false ;
    }
     private  void  init(){

        scroller =  new Scroller(getContext());

        task =  new TimerTask() {
             @Override
             public  void  run() {
                 if( isAuto ){
                    autoHandler.sendEmptyMessage( 0);
                }
            }
        };

        timer.schedule(task ,  100 ,  3000 );

    }

     @Override
     public  void  computeScroll() {
         super.computeScroll();
         if( scroller.computeScrollOffset()){
            scrollTo(index * childWidth,  0);
            invalidate();
        }
    }

     public  ImageBannerViewGroup(Context context) {
         super(context);
        init();
    }

     public  ImageBannerViewGroup(Context context, AttributeSet attrs) {
         super(context, attrs);
        init();
    }

     public  ImageBannerViewGroup(Context context, AttributeSet attrs,  int defStyleAttr) {
         super(context, attrs, defStyleAttr);
        init();
    }

     @Override
     protected  void  onMeasure( int widthMeasureSpec,  int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);

         //求出子视图的个数
        children = getChildCount();
         if( children ==  0 ){
            setMeasuredDimension( 0, 0);
        } else{
             //测量子视图的高度和宽度
            measureChildren(widthMeasureSpec,heightMeasureSpec);

             //根据子视图的宽度和高度 , 求出该ViewGroup的宽度和高度
            View view = getChildAt( 0);

            childHeight = view.getMeasuredHeight() ;
            childWidth = view.getMeasuredWidth() ;
             //子视图的总宽度
             int width = childWidth * children ;
            setMeasuredDimension( width , childHeight );
        }
    }

     /**
     *
     *  @param change 布局位置发生改变时为true
     *  @param l  相对于父View的Left位置
     *  @param t  相对于父View的Top位置
     *  @param r  相对于父View的Right位置
     *  @param b  相对于父View的Bottom位置
     */

     @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 ;

            }

        }
    }

     @Override
     protected  void  onDraw(Canvas canvas) {
         super.onDraw(canvas);
    }

     /**
     * 该方法返回true , ViewGroup会处理此次拦截事件
     */

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

     @Override
     public  boolean  onTouchEvent(MotionEvent event) {
         switch ( event.getAction() ){
             case MotionEvent.ACTION_DOWN:
                isClick =  true ;
                 stopAuto();
                 if( !scroller.isFinished() ){
                    scroller.abortAnimation();
                }

                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() ;
                index = ( scrollX + childWidth/ 2 ) / childWidth ;

                 if( index <  0 ){
                    index =  0 ;
                } else  if( index > children -  1 ){
                    index = children -  1 ;
                }
                 if( isClick ){
                     //如果是点击事件
                    listner.clickImageIndex(index);
                }
                 else{
                     int dx = index * children - scrollX ;
                    scroller.startScroll(scrollX ,  0 , dx ,  0 );
                    postInvalidate();
                    bannerViewGroupListener.selectImage(index);
                }

                startAuto();

             /*    scrollTo( index * childWidth , 0 );*/
                 break ;
             default:

                 break ;

        }

         //返回true的目的是告诉该ViewGroup的父View已经处理该事件
         return  true ;
    }

     //
     public   interface  ImageBannerViewGroupListener{
         void selectImage(  int index ) ;
    }

}

     FrameLayout类        

   package com.imagebanner;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import java.util.List;

/**
* Created by Administrator on 2017/7/9.
*/


public   class  ImageBannerFrameLayout  extends FrameLayout  implements  ImageBannerViewGroup. ImageBannerViewGroupListener ,  ImageBannerViewGroup. ImageBannerListner{

     private ImageBannerViewGroup imageBannerViewGroup ;

     private LinearLayout linearLayout ;

     //自定义轮播图的监听器
     public FrameLayoutListener listener ;

     public FrameLayoutListener  getListener() {
         return listener;
    }

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

     public  ImageBannerFrameLayout(Context context) {
         super(context);
        initViewGroup();
        initDotLinearLayout();
    }

     public  ImageBannerFrameLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
        initViewGroup();
        initDotLinearLayout();
    }

     public  ImageBannerFrameLayout(Context context, AttributeSet attrs,  int defStyleAttr) {
         super(context, attrs, defStyleAttr);
        initViewGroup();
        initDotLinearLayout();
    }

     //初始化图片轮播功能的核心类
     private  void  initViewGroup(){
        imageBannerViewGroup =  new ImageBannerViewGroup(getContext());
         //设置布局属性
        FrameLayout.LayoutParams fp =  new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        imageBannerViewGroup.setLayoutParams(fp);
         //为ViewGroup设置底部圆点切换的监听器
        imageBannerViewGroup.setBannerViewGroupListener( this);
         //为ViewGroup设置图片点击事件的监听器
        imageBannerViewGroup.setListner( this);
        addView(imageBannerViewGroup);
    }

     //初始化底部圆点布局
     private  void  initDotLinearLayout(){

        linearLayout =  new LinearLayout(getContext());
         //设置布局属性
        FrameLayout.LayoutParams fp =  new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,  40);
        linearLayout.setLayoutParams(fp);
        linearLayout.setOrientation(LinearLayout.HORIZONTAL);
        linearLayout.setGravity(Gravity.CENTER);

        linearLayout.setBackgroundColor(Color.GRAY);

        addView(linearLayout);

        FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
        layoutParams.gravity = Gravity.BOTTOM ;

        linearLayout.setLayoutParams(layoutParams);

         if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            linearLayout.setAlpha(  0.5f );
        } else{
            linearLayout.getBackground().setAlpha( 100);
        }

    }

     //公共方法,向FrameLayout中添加图片
     public  void  addBitmap( List<Bitmap> list ){

         for(  int i =  0 ; i < list.size() ; i ++ ){

            Bitmap bitmap = list.get(i);

            addBitmapToViewGroup(bitmap);

            addDots();
        }
    }

     //向ViewGroup中添加图片
     private  void  addBitmapToViewGroup(Bitmap bitmap){
        ImageView iv =  new ImageView(getContext());
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        iv.setLayoutParams(  new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        iv.setImageBitmap(bitmap);
        imageBannerViewGroup.addView(iv);
    }

     //向底部linearlayout中添加圆点
     private  void  addDots(){
        ImageView iv =  new ImageView(getContext());
        LinearLayout.LayoutParams lp =  new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        lp.setMargins( 15, 5, 15, 5);
        iv.setLayoutParams(lp);
        iv.setImageResource(R.drawable.dot_normal);
        linearLayout.addView(iv);
    }

     //实现该接口,完成底部圆点的切换
     @Override
     public  void  selectImage( int index) {
         int count = linearLayout.getChildCount();
         for(  int i =  0 ; i < count ; i ++ ){
            ImageView iv = (ImageView) linearLayout.getChildAt(i);
             if( i == index ){
                iv.setImageResource(R.drawable.dot_select);
            } else {
                iv.setImageResource(R.drawable.dot_normal);
            }
        }
    }

         @Override
     public  void  clickImageIndex( int pos) {
        listener.clickImageIndex(pos);
    }

     //定义FrameLayout的监听器接口
     public   interface  FrameLayoutListener{

         void clickImageIndex(  int pos ) ;
    }
}

     MainActivity代码                

   package com.imagebanner;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public   class  MainActivity  extends AppCompatActivity  implements  ImageBannerFrameLayout. FrameLayoutListener{

     private ImageBannerFrameLayout mGroup ;

     private  int[] ids =  new  int[]{
            R.drawable.banner1,
            R.drawable.banner2,
            R.drawable.banner3
    };

     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         //计算出当前手机的宽度
        DisplayMetrics dm =  new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
         int width = dm.widthPixels;

        mGroup = (ImageBannerFrameLayout) findViewById(R.id.image_banner);
        mGroup.setListener( this);

        List<Bitmap> list =  new ArrayList<>();
         for(  int i =  0 ; i < ids.length ; i ++ ){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),ids[i]);
            list.add(bitmap);
        }

        mGroup.addBitmap(list);

    }

     //此处填写点击事件相关的业务代码
     @Override
     public  void  clickImageIndex( int pos) {
          Toast.makeText( this, "点击了第" + pos +   "张图片" , Toast.LENGTH_SHORT).show();
    }
}

     圆点布局文件dot_normal.xml        

    <?xml version="1.0" encoding="utf-8"?>
< shape  xmlns:android= "http://schemas.android.com/apk/res/android"
     android:shape= "oval">
     < solid  android:color= "@android:color/white"> </ solid>

     < size  android:height= "10dp"
         android:width= "10dp"> </ size>

</ shape>

     dot_select.xml                

  <?xml version="1.0" encoding="utf-8"?>
< shape  xmlns:android= "http://schemas.android.com/apk/res/android"
     android:shape= "oval">
     < solid  android:color= "@android:color/holo_red_light"> </ solid>

     < size  android:height= "10dp"
         android:width= "10dp"> </ size>

</ shape>

     MainActivity布局文件                

  <?xml version="1.0" encoding="utf-8"?>
< RelativeLayout  xmlns:android= "http://schemas.android.com/apk/res/android"
     xmlns:tools= "http://schemas.android.com/tools"
     android:id= "@+id/activity_main"
     android:layout_width= "match_parent"
     android:layout_height= "match_parent"
     tools:context= "com.imagebanner.MainActivity">

     < com.imagebanner.ImageBannerFrameLayout
         android:id= "@+id/image_banner"
         android:layout_width= "match_parent"
         android:layout_height= "200dp"> </ com.imagebanner.ImageBannerFrameLayout>
</ RelativeLayout>

----------------------------
原文链接:https://blog.csdn.net/SakuraMashiro/article/details/75069155

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-03-08 23:27:41 重新编辑]
  Java面向对象编程-->类的生命周期
  JavaWeb开发-->JavaWeb应用入门(Ⅱ)
  JSP与Hibernate开发-->JPA API的高级用法
  Java网络编程-->创建非阻塞的HTTP服务器
  精通Spring-->绑定表单
  Vue3开发-->CSS过渡和动画
  Android Gallery实现循环显示图像
  Android多线程及异步处理问题
  Android Fragments 详细使用
  Android ExpandableListView 使用范例
  编译Irrlicht On Android
  Android UI学习 - Menu
  Android自定义组件
  Android API 中Toast类的用法
  从Android中Activity之间的通信说开来
  Android语音识别 android.speech 包分析
  UI渲染优化
  Android UI优化—从Android渲染原理理解UI卡顿
  Android 加载大图/多图,有效避免OOM
  Android 碎片(Fragment)
  Android强制设置横屏或竖屏-Alex_Michel
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。