Android implements carousel control Banner

background

I recently made a requirement to implement a carousel chart, and finally realized the requirement through Handler+ViewPager, so I will summarize the implementation process for future reference. The following is the effect of the carousel chart:

carousel

Implementation ideas

  • Scheduled carousel
    uses Handler+ViewPager, Handler sends scheduled messages to switch ViewPager pages

  • Infinite carousel effect
    Insert image description here

If we just automatically rotate to the last page and then make a judgment to switch to the first page, we can achieve the effect of the carousel, but there are two problems
when switching from the last page to the first page. One of them is obvious. The scrollback effect is not what we want.
When we manually slide, we cannot continue to slide left and right on the first and last pages because there is no next page.

Insert a last page before the first page and a first page after the last page, so that when the user continues to slide to the right on the first page, they can slide to see the last page. Similarly, when the user continues to slide to the right on the first page, the user can slide to the last page. When you slide the page to the left, you can slide to the first page.

Although the user only sees three pictures, there are actually five. When it automatically switches to view5 in view4, it makes a judgment and switches to view2. This creates the feeling that the last picture is the first. When switching to view1 in view2, make a judgment to switch to view4.

We use the viewpage's own method to switch the interface and switch immediately without scrolling effect. When the picture is the same, you cannot see the change of the picture.

setCurrentItem(int item, boolean smoothScroll)
第二个参数设置false 界面切换的时候无滚动效果 默认true

The following is the code to implement
layout_banner.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/banner_view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_gravity="bottom"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:background="#33000000">
        <!-- 标题-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:layout_marginStart="10dp"
            android:layout_gravity="start"
            android:id="@+id/banner_title"
            tools:text="title"/>
        <!-- 小圆点-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/banner_indicator"
            android:orientation="horizontal"
            android:gravity="end"
            android:layout_marginEnd="10dp"
            android:padding="10dp">
        </LinearLayout>
    </LinearLayout>

</FrameLayout>

BannerViewPager.java

package com.ryd.banner.banner;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import com.ryd.banner.R;
import com.ryd.banner.Utils;

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

/**
 * @author : ruanyandong
 * @e-mail : [email protected]
 * @date : 12/29/22 4:23 PM
 * @desc : com.ryd.banner
 */
public class BannerViewPager extends FrameLayout {
    
    

    private ViewPager viewPager;
    private TextView tvTitle;
    private LinearLayout indicatorGroup;
    private BannerAdapter adapter;
    private List<String> titles;//标题集合
    private List imageUrls;//图片数据
    private List<View> views;//轮播图显示
    private ImageView[] tips;//保存显示的小圆点
    private int count;//保存imageUrls的总数
    private int bannerTime = 2500;//轮播图的间隔时间
    private int currentItem = 0;//轮播图的当前选中项
    private long releaseTime = 0;//保存触发时手动滑动的时间 进行判断防止滑动之后立即轮播
    private final int START = 10;
    private final int STOP = 20;
    private Context context;
    private Handler handler;

    private final Runnable runnable = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            long now = System.currentTimeMillis();
            if (now - releaseTime > bannerTime - 500) {
    
    
                handler.sendEmptyMessage(START);
            } else {
    
    
                handler.sendEmptyMessage(STOP);
            }
        }
    };


    public BannerViewPager(Context context) {
    
    
        super(context);
    }

    public BannerViewPager(Context context, AttributeSet attrs) {
    
    
        super(context, attrs);
        this.context = context;
        titles = new ArrayList<>();
        titles.add("标题1");
        titles.add("标题2");
        titles.add("标题3");
        imageUrls = new ArrayList();
        views = new ArrayList<>();
        init(context, attrs);
    }


    private void init(final Context context, AttributeSet attrs) {
    
    
        View view = LayoutInflater.from(context).inflate(R.layout.layout_banner, this);
        viewPager = (ViewPager) view.findViewById(R.id.banner_view_pager);
        tvTitle = (TextView) view.findViewById(R.id.banner_title);
        indicatorGroup = (LinearLayout) view.findViewById(R.id.banner_indicator);
        handler = new Handler(Looper.getMainLooper()) {
    
    
            @Override
            public void handleMessage(Message msg) {
    
    
                super.handleMessage(msg);
                switch (msg.what) {
    
    
                    case START:
                        viewPager.setCurrentItem(currentItem + 1);
                        handler.removeCallbacks(runnable);
                        handler.postDelayed(runnable, bannerTime);
                        break;
                    case STOP:
                        releaseTime = 0;
                        handler.removeCallbacks(runnable);
                        handler.postDelayed(runnable, bannerTime);
                        break;
                }
            }
        };
    }

    /**
     * 初始化数据 以及拿到数据后的各种设置
     * 可以是网络地址 也可是项目图片数据
     *
     * @param imageUrls
     */
    public void setData(List<?> imageUrls) {
    
    
        this.imageUrls.clear();
        this.count = imageUrls.size();

        //this.imageUrls.add(imageUrls.get(count-1));
        this.imageUrls.addAll(imageUrls);
        //this.imageUrls.add(imageUrls.get(0));

        initIndicator();
        getShowView();
        setUI();
    }

    /**
     * 设置标题
     *
     * @param titles
     */
    public void setTitles(List<String> titles) {
    
    
        this.titles.clear();
        this.titles.addAll(titles);
    }

    /**
     * 设置小圆点指示器
     */
    private void initIndicator() {
    
    
        tips = new ImageView[count-2];
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        layoutParams.height = Utils.dip2px(context,10);
        layoutParams.width = Utils.dip2px(context,10);
        layoutParams.leftMargin = Utils.dip2px(context,5);// 设置点点点view的左边距
        layoutParams.rightMargin = Utils.dip2px(context,5);// 设置点点点view的右边距
        for (int i = 0; i < count-2; i++) {
    
    
            ImageView imageView = new ImageView(context);
            if (i == 0) {
    
    
                imageView.setBackgroundResource(R.drawable.shape_circle_red);
            } else {
    
    
                imageView.setBackgroundResource(R.drawable.shape_circle_white);
            }

            tips[i] = imageView;
            indicatorGroup.addView(imageView, layoutParams);
        }
    }

    /**
     * 获取显示图片view
     */
    private void getShowView() {
    
    
        for (int i = 0; i < imageUrls.size(); i++) {
    
    
            ImageView imageView = new ImageView(context);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            if (imageUrls.get(i) instanceof String) {
    
    

            } else {
    
    
                imageView.setImageResource((Integer) imageUrls.get(i));
            }
            views.add(imageView);
        }
    }

    /**
     * 设置UI
     */
    private void setUI() {
    
    
        adapter = new BannerAdapter();
        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(onPageChangeLis);
        viewPager.setCurrentItem(1);
        handler.postDelayed(runnable, bannerTime);
    }


    // 第一页到第二页
    //  onPageScrollStateChanged: state 1
    //  onPageScrolled: position 0 positionOffset 0.031481482 positionOffsetPixels 34
    //  onPageScrolled: position 0 positionOffset 0.107407406 positionOffsetPixels 116
    //  onPageScrolled: position 0 positionOffset 0.18888889 positionOffsetPixels 204
    //  onPageScrolled: position 0 positionOffset 0.20462963 positionOffsetPixels 221
    //  onPageScrollStateChanged: state 2
    //  onPageSelected: position 1
    //  onPageScrolled: position 0 positionOffset 0.28148147 positionOffsetPixels 304
    //  onPageScrolled: position 0 positionOffset 0.44351852 positionOffsetPixels 479
    //  onPageScrolled: position 0 positionOffset 0.5685185 positionOffsetPixels 614
    //  onPageScrolled: position 0 positionOffset 0.675 positionOffsetPixels 729
    //  onPageScrolled: position 0 positionOffset 0.7592593 positionOffsetPixels 820
    //  onPageScrolled: position 0 positionOffset 0.82222223 positionOffsetPixels 888
    //  onPageScrolled: position 0 positionOffset 0.87314814 positionOffsetPixels 943
    //  onPageScrolled: position 0 positionOffset 0.912037 positionOffsetPixels 985
    //  onPageScrolled: position 0 positionOffset 0.9388889 positionOffsetPixels 1014
    //  onPageScrolled: position 0 positionOffset 0.96018517 positionOffsetPixels 1037
    //  onPageScrolled: position 0 positionOffset 0.975 positionOffsetPixels 1053
    //  onPageScrolled: position 0 positionOffset 0.98425925 positionOffsetPixels 1063
    //  onPageScrolled: position 0 positionOffset 0.9916667 positionOffsetPixels 1071
    //  onPageScrolled: position 0 positionOffset 0.9953704 positionOffsetPixels 1075
    //  onPageScrolled: position 0 positionOffset 0.99814814 positionOffsetPixels 1078
    //  onPageScrolled: position 0 positionOffset 0.9990741 positionOffsetPixels 1079
    //  onPageScrolled: position 1 positionOffset 0.0 positionOffsetPixels 0
    //  onPageScrollStateChanged: state 0

    // 第二页到第三页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 1 positionOffset 0.01111114 positionOffsetPixels 12
    // onPageScrolled: position 1 positionOffset 0.06666672 positionOffsetPixels 72
    // onPageScrolled: position 1 positionOffset 0.1240741 positionOffsetPixels 134
    // onPageScrolled: position 1 positionOffset 0.19259262 positionOffsetPixels 208
    // onPageScrolled: position 1 positionOffset 0.2527778 positionOffsetPixels 273
    // onPageScrolled: position 1 positionOffset 0.27592587 positionOffsetPixels 297
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 2
    // onPageScrolled: position 1 positionOffset 0.30277777 positionOffsetPixels 327
    // onPageScrolled: position 1 positionOffset 0.4074074 positionOffsetPixels 440
    // onPageScrolled: position 1 positionOffset 0.4990741 positionOffsetPixels 539
    // onPageScrolled: position 1 positionOffset 0.57407403 positionOffsetPixels 619
    // onPageScrolled: position 1 positionOffset 0.64444447 positionOffsetPixels 696
    // onPageScrolled: position 1 positionOffset 0.70092595 positionOffsetPixels 757
    // onPageScrolled: position 1 positionOffset 0.7537037 positionOffsetPixels 814
    // onPageScrolled: position 1 positionOffset 0.79814816 positionOffsetPixels 862
    // onPageScrolled: position 1 positionOffset 0.8342593 positionOffsetPixels 901
    // onPageScrolled: position 1 positionOffset 0.8666667 positionOffsetPixels 936
    // onPageScrolled: position 1 positionOffset 0.89351857 positionOffsetPixels 965
    // onPageScrolled: position 1 positionOffset 0.91481483 positionOffsetPixels 988
    // onPageScrolled: position 1 positionOffset 0.9342593 positionOffsetPixels 1009
    // onPageScrolled: position 1 positionOffset 0.94907403 positionOffsetPixels 1025
    // onPageScrolled: position 1 positionOffset 0.96111107 positionOffsetPixels 1038
    // onPageScrolled: position 1 positionOffset 0.9703704 positionOffsetPixels 1048
    // onPageScrolled: position 1 positionOffset 0.97870374 positionOffsetPixels 1057
    // onPageScrolled: position 1 positionOffset 0.98425925 positionOffsetPixels 1063
    // onPageScrolled: position 1 positionOffset 0.98888886 positionOffsetPixels 1068
    // onPageScrolled: position 1 positionOffset 0.9925926 positionOffsetPixels 1072
    // onPageScrolled: position 1 positionOffset 0.9944445 positionOffsetPixels 1074
    // onPageScrolled: position 1 positionOffset 0.9962963 positionOffsetPixels 1076
    // onPageScrolled: position 1 positionOffset 0.9981482 positionOffsetPixels 1078
    // onPageScrolled: position 1 positionOffset 0.9990741 positionOffsetPixels 1079
    // onPageScrolled: position 2 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0

    // 第三页到第二页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 1 positionOffset 0.95277774 positionOffsetPixels 1029
    // onPageScrolled: position 1 positionOffset 0.88796294 positionOffsetPixels 959
    // onPageScrolled: position 1 positionOffset 0.83611107 positionOffsetPixels 902
    // onPageScrolled: position 1 positionOffset 0.82592595 positionOffsetPixels 892
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 1
    // onPageScrolled: position 1 positionOffset 0.7851852 positionOffsetPixels 848
    // onPageScrolled: position 1 positionOffset 0.66388893 positionOffsetPixels 717
    // onPageScrolled: position 1 positionOffset 0.55277777 positionOffsetPixels 597
    // onPageScrolled: position 1 positionOffset 0.45648146 positionOffsetPixels 492
    // onPageScrolled: position 1 positionOffset 0.3787037 positionOffsetPixels 409
    // onPageScrolled: position 1 positionOffset 0.30833328 positionOffsetPixels 332
    // onPageScrolled: position 1 positionOffset 0.2490741 positionOffsetPixels 269
    // onPageScrolled: position 1 positionOffset 0.20092595 positionOffsetPixels 217
    // onPageScrolled: position 1 positionOffset 0.1592592 positionOffsetPixels 171
    // onPageScrolled: position 1 positionOffset 0.1240741 positionOffsetPixels 134
    // onPageScrolled: position 1 positionOffset 0.09722221 positionOffsetPixels 104
    // onPageScrolled: position 1 positionOffset 0.07407403 positionOffsetPixels 79
    // onPageScrolled: position 1 positionOffset 0.055555582 positionOffsetPixels 60
    // onPageScrolled: position 1 positionOffset 0.041666627 positionOffsetPixels 44
    // onPageScrolled: position 1 positionOffset 0.030555606 positionOffsetPixels 33
    // onPageScrolled: position 1 positionOffset 0.021296263 positionOffsetPixels 22
    // onPageScrolled: position 1 positionOffset 0.014814854 positionOffsetPixels 16
    // onPageScrolled: position 1 positionOffset 0.010185242 positionOffsetPixels 11
    // onPageScrolled: position 1 positionOffset 0.0064815283 positionOffsetPixels 7
    // onPageScrolled: position 1 positionOffset 0.004629612 positionOffsetPixels 4
    // onPageScrolled: position 1 positionOffset 0.0027778149 positionOffsetPixels 3
    // onPageScrolled: position 1 positionOffset 9.2589855E-4 positionOffsetPixels 0
    // onPageScrolled: position 1 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0

    // 第二页到第一页
    // onPageScrollStateChanged: state 1
    // onPageScrolled: position 0 positionOffset 0.9592593 positionOffsetPixels 1036
    // onPageScrolled: position 0 positionOffset 0.8666667 positionOffsetPixels 936
    // onPageScrolled: position 0 positionOffset 0.75185186 positionOffsetPixels 812
    // onPageScrolled: position 0 positionOffset 0.7185185 positionOffsetPixels 776
    // onPageScrollStateChanged: state 2
    // onPageSelected: position 0
    // onPageScrolled: position 0 positionOffset 0.65185183 positionOffsetPixels 704
    // onPageScrolled: position 0 positionOffset 0.46203703 positionOffsetPixels 499
    // onPageScrolled: position 0 positionOffset 0.31851852 positionOffsetPixels 344
    // onPageScrolled: position 0 positionOffset 0.21851853 positionOffsetPixels 236
    // onPageScrolled: position 0 positionOffset 0.14166667 positionOffsetPixels 153
    // onPageScrolled: position 0 positionOffset 0.08796296 positionOffsetPixels 95
    // onPageScrolled: position 0 positionOffset 0.053703703 positionOffsetPixels 58
    // onPageScrolled: position 0 positionOffset 0.030555556 positionOffsetPixels 33
    // onPageScrolled: position 0 positionOffset 0.015740741 positionOffsetPixels 17
    // onPageScrolled: position 0 positionOffset 0.0074074073 positionOffsetPixels 8
    // onPageScrolled: position 0 positionOffset 0.0027777778 positionOffsetPixels 3
    // onPageScrolled: position 0 positionOffset 9.259259E-4 positionOffsetPixels 1
    // onPageScrolled: position 0 positionOffset 0.0 positionOffsetPixels 0
    // onPageScrollStateChanged: state 0
    /**
     * viewPage改变监听
     * 用于响应所选页面
     */
    private ViewPager.OnPageChangeListener onPageChangeLis = new ViewPager.OnPageChangeListener() {
    
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    
    
            Log.d("ruanyandong", "onPageScrolled: position "+position+" positionOffset "+positionOffset+" positionOffsetPixels "+positionOffsetPixels);
            // 第一页到第二页  position 0->1  positionOffset 0->1->0  positionOffsetPixels 0->划过一页的距离->0
            // 第二页到第三页  position 1->2  positionOffset 0->1->0  positionOffsetPixels 0->划过一页的距离->0
            // 第三页到第二页  position 一直是1  positionOffset 1->0  positionOffsetPixels 一页的距离->0
            // 第二页到第一页  position 一直是0  positionOffset 1->0  positionOffsetPixels 一页的距离->0
        }

        @Override
        public void onPageSelected(int position) {
    
    
            Log.d("ruanyandong", "onPageSelected: position "+position);
            // 每滑动一次都只打印一次方法,position打印最终选定页面的下标

            //计算当前页的下标,用于指示器改变
            int max = views.size() - 1;
            int temp = position;
            currentItem = position;
            if (position == 0) {
    
     // 当滑动到下标为0时,其实对于用户来说看到的是最后一个,所以需要将下标替换成倒数第二个
                currentItem = max - 1;
            } else if (position == max) {
    
    // 当滑动到最后一个时,其实对于用户来说看到的是第一个,所以需要将下标替换成正数第二个
                currentItem = 1;
            }
            temp = currentItem - 1;
            setIndicatorAndTitle(temp);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
    
    
            currentItem = viewPager.getCurrentItem();
            Log.d("ruanyandong", "onPageScrollStateChanged: state "+state+" currentItem "+currentItem);
            // 0 静止  1 正在拖拽   2 松手后自动滑动
            // 每次拖动的状态变化都是 1->2->0  每次拖动一页,都会调用3次方法,依次打印1->2->0
            switch (state) {
    
    
                case 0: // 静止状态
                    //Log.e("aaa","=====静止状态======");
                    if (currentItem == 0) {
    
    
                        viewPager.setCurrentItem(count-2, false);
                    } else if (currentItem == count - 1) {
    
    
                        viewPager.setCurrentItem(1, false);
                    }
                    break;
                case 1: // 拖拽状态
//    Log.e("aaa","=======手动拖拽滑动时调用====");
                    releaseTime = System.currentTimeMillis();
                    if (currentItem == count - 1) {
    
    
                        viewPager.setCurrentItem(1, false);
                    } else if (currentItem == 0) {
    
    
                        viewPager.setCurrentItem(count-2, false);
                    }
                    break;
                case 2: // 松手自动滑动状态
//    Log.e("aaa","=======自动滑动时调用====");
                    break;
            }
        }
    };


    /**
     * 设置指示器和标题切换
     *
     * @param position
     */
    private void setIndicatorAndTitle(int position) {
    
    
        tvTitle.setText(titles.get(position));

        for (int i = 0; i < tips.length; i++) {
    
    
            if (i == position) {
    
    
                tips[i].setBackgroundResource(R.drawable.shape_circle_red);
            } else {
    
    
                tips[i].setBackgroundResource(R.drawable.shape_circle_white);
            }
        }
    }

    /**
     * 适配器
     */
    class BannerAdapter extends PagerAdapter {
    
    

        /**
         * 返回可用的视图数量
         * @return
         */
        @Override
        public int getCount() {
    
    
            return views.size();
        }


        /**
         * 判断当前View是否是来自于object
         * @param view
         * @param object
         * @return
         */
        @Override
        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
    
    
            return view == object;
        }

        /**
         * 创建初始化View,如果总共有三个View,刚开始会创建好第一二两个,当从第一个滑动到第二个时,会创建第三个view
         * @param container
         * @param position
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
    
    
            container.addView(views.get(position));
            return views.get(position);
        }

        /**
         * 销毁view,当滑动到第三个时会销毁第一个,当滑动到第一个时会销毁第三个
         * @param container
         * @param position
         * @param object
         */
        @Override
        public void destroyItem(ViewGroup container, int position, @NonNull Object object) {
    
    
            container.removeView((View) object);
        }


    }

}

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".banner.MainActivity">

    <com.ryd.banner.banner.BannerViewPager
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>

</FrameLayout>

MainActivity.java

package com.ryd.banner.banner;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import com.ryd.banner.R;
import com.ryd.banner.viewpager2.ViewPager2Activity;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    
    

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

        BannerViewPager banner= (BannerViewPager) findViewById(R.id.banner);
        List<Integer> imageUrl=new ArrayList<>();
        imageUrl.add(R.drawable.water); // 当用户在第一页时,向右拖拽左滑时新增一个最后一页,让用户以为滑到了最后一页
        imageUrl.add(R.drawable.bubble);// 1 用户看到的第一页
        imageUrl.add(R.drawable.grass);// 2 用户看到的第二页
        imageUrl.add(R.drawable.water);// 3 用户看到的第三页
        imageUrl.add(R.drawable.bubble);// 当用户在第三页时,向左拖拽右滑时新增一个第一页,让用户以为滑到了第一页
        banner.setData(imageUrl);

    }


}

Finally, the github address of the code is attached
. Please refer to
the simple implementation of android carousel chart.

Guess you like

Origin blog.csdn.net/qq_34681580/article/details/128525390