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

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

效果图
这里写图片描述

主要的思路如下:
首先,轮播图可以理解为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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值