Android使用viewpager实现图片轮播效果

自定义View实现图片轮播,实现了图片自动轮播,手动滑动,轮播标题,以及点击事件。

里面有很多注释

一、文件布局

二、代码

ImageBannerViewGroup类

/**
 * Created by hp on 2018/7/31.
 * 这是实现图片轮播的核心类
 */

public class ImageBannerViewGroup extends ViewGroup
{

    private int children;//我们ViewGroup子视图的总个数
    private int childwidth;//子视图的宽度
    private int childheight;//子视图的高度


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

    private Scroller scroller;//.利用 Scroller 对象 完成轮播图的手动轮播
    /**
     * 要实现图片的单击事件的获取
     * 我们 利用一个单击变量开关进行判断,在用户离开屏幕的一瞬间
     * 我们用判断变量开关来判断用户的操作是点击还是移动
     */

    private boolean isClick;//true代表的是点击事件,false代表的是不是点击事件
    private ImageBarnnerLister lister;

    public ImageBarnnerLister getLister(){
        return lister;
    }

    public void setLister(ImageBarnnerLister lister) {
        this.lister = lister;
    }

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

    private ImageBarnnerViewGroupLisnner barnnerViewGroupLisnner;

    public ImageBarnnerViewGroupLisnner getBarnnerViewGroupLisnner() {
        return barnnerViewGroupLisnner;
    }

    public void setBarnnerViewGroupLisnner(ImageBarnnerViewGroupLisnner barnnerViewGroupLisnner) {
        this.barnnerViewGroupLisnner = barnnerViewGroupLisnner;
    }
    /**
     * 实现图片轮播底部圆点以及底部圆点切换功能步骤思路:
     * 1.自定义一个集成自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的iew最终显示的是最后一个view)
     *   利用这个特性我们就可以实现底部圆点的布局
     * 2.我们需要准备素材,就是底部圆点的素材,我们可以利用Drawable的功能,去实现一个圆点的展示
     * 3.我们需要继承FrameLayout来定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBannerViewGroup核心类
     *    和我们需要实现的底部圆点的布局LinearLayout来实现
     */
    //---自动轮播
    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);
                    barnnerViewGroupLisnner.selectImage(index);
                    break;
            }
        }
    };
    private void startAuto(){
        isAuto = true;
    }
    private void stopAuto(){
        isAuto = false;
    }

    /**
     * 采用Timer,TimerTask,Handler三者相结合的方法实现自动轮播
     * 抽取两个方法来控制,是否启动自动轮播,称之为 startAuto(),stopAuto();
     * 我们在2个方法的控制过程中,我们希望能有控制自动开启轮播图的开关
     * 我们需要一个变量参数来作为我们自动启动轮播图的开关,为 isAuto boolean  true代表开启,false代表关闭
     */



    public ImageBannerViewGroup(Context context) {
        super(context);
        initObj();//.利用 Scroller 对象 完成轮播图的手动轮播需要用到这个方法,利用 scrollTo、scrollBy 完成轮播图的手动轮播时可以删掉
    }

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

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

    private void initObj() {
        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() {//利用 Scroller 对象 完成轮播图的手动轮播需要用到这个方法
        super.computeScroll();
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),0);
            invalidate();
        }
    }

    /**
     * 我们在自定义的ViewGroop中,我们必须要实现的方法有:测量-》布局-》绘制
     * 那么对于来说就是: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);//因为此时第一个视图绝对是存在的
            childwidth = view.getMeasuredWidth();

            //3.根据子视图的宽度和高度,求出改ViewGroup的宽度和高度
            childheight = view.getMeasuredHeight();
            int width = view.getMeasuredWidth() * children;
            setMeasuredDimension(width,childheight);

        }

    }
    /**
     * 事件传递过程中的调用方法,我们需要调用容器的拦截方法 onInterceptTouchEvent
     * 针对于该方法我们可以理解为 如果说该方法的返回值为true的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件
     * 如果说返回值为false的时候,那么我们自定义的ViewGroup容器将不会接受此次事件的处理过程,将会继续向下传递事件
     * 针对于我们自定义的ViewGroup 我们希望我们的ViewGroup,容器处理接受事件 那么我们的返回值就是true
     */
    @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) {
//        return super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN://表示的是用户按下的一瞬间
                stopAuto();
                if(scroller.isFinished()){//.利用 Scroller 对象 完成轮播图的手动轮播
                    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();
                index = (scrollX + childwidth / 2) / childwidth;
                if(index < 0) {//说明了此时已经滑倒了最左边第一张图片
                    index = 0;
                }
                else if(index > children - 1) {//说明滑到了最后一张图片
                    index = children - 1;
                }
                if (isClick){//代表点击事件
                    lister.clickImageIndex(index);
                }
                else {
                    int dx = index * childwidth - scrollX;//.利用 Scroller 对象 完成轮播图的手动轮播
                    scroller.startScroll(scrollX,0,dx,0);//.利用 Scroller 对象 完成轮播图的手动轮播
                    postInvalidate();//.利用 Scroller 对象 完成轮播图的手动轮播
//                scrollTo(index * childwidth,0);//利用 scrollTo、scrollBy 完成轮播图的手动轮播
                    barnnerViewGroupLisnner.selectImage(index);
                }

                startAuto();
                break;
            default:
                break;
        }
        return true;//我们该容器的父View,我们已经处理好了该事件

    }

    /**
     * 继承ViewGroup必须要实现布局onLayout方法
     * @param changed 当我们的ViewGroup布局位置发生变化的为true,没有发生改变为false
     * @param l
     * @param t
     * @param r
     * @param b ,没有发生改变为false
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(changed){

            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 ImageBarnnerViewGroupLisnner{
        void selectImage(int index);
    }
}


ImageBarnnerFrameLayout类

/**
 * Created by hp on 2018/8/2.
 */

public class ImageBarnnerFrameLayout extends FrameLayout implements ImageBannerViewGroup.ImageBarnnerViewGroupLisnner,ImageBannerViewGroup.ImageBarnnerLister{

    private ImageBannerViewGroup imageBannerViewGroup;
    private LinearLayout linearLayout;

    private FramLayoutLisenner lisenner;

    public FramLayoutLisenner getLisenner() {
        return lisenner;
    }

    public void setLisenner(FramLayoutLisenner lisenner) {
        this.lisenner = lisenner;
    }

    public ImageBarnnerFrameLayout(@NonNull Context context) {
        super(context);
        initImageBannerViewGroup();
        initDotLinearlayout();
    }

    public ImageBarnnerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initImageBannerViewGroup();
        initDotLinearlayout();
    }

    public ImageBarnnerFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initImageBannerViewGroup();
        initDotLinearlayout();
    }

    public void addBitmaps(List<Bitmap> list){

        for(int i = 0;i < list.size();i++){
            Bitmap bitmap = list.get(i);
            addBitmapToImageBarnnerViewGroup(bitmap);
            addDotToLinearlayout();
        }

    }
    private void addDotToLinearlayout(){
        ImageView iv = new ImageView(getContext());
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        lp.setMargins(5,5,5,5);
        iv.setLayoutParams(lp);
        iv.setImageResource(R.drawable.dot_normal);
        linearLayout.addView(iv);

    }

    private void addBitmapToImageBarnnerViewGroup(Bitmap bitmap){

        ImageView iv = new ImageView(getContext());
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        iv.setLayoutParams(new ViewGroup.LayoutParams(C.WITTH,ViewGroup.LayoutParams.WRAP_CONTENT));
        iv.setImageBitmap(bitmap);
        imageBannerViewGroup.addView(iv);
    }

    /**
     * 初始化我们自定义的图片轮播功能的核心
     */
    private void initImageBannerViewGroup(){
        imageBannerViewGroup = new ImageBannerViewGroup(getContext());
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        imageBannerViewGroup.setLayoutParams(lp);
        imageBannerViewGroup.setBarnnerViewGroupLisnner(this);//这里就是将Lisnner传递给Framlayout
        imageBannerViewGroup.setLister(this);
        addView(imageBannerViewGroup);
    }

    /**
     * 初始化 底部圆点 布局
     */
    private void initDotLinearlayout(){
        linearLayout = new LinearLayout(getContext());
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(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);
        }

    }

    @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) {
        lisenner.clickImageIndex(pos);
    }

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

 MainActivity类

public class MainActivity extends AppCompatActivity implements ImageBarnnerFrameLayout.FramLayoutLisenner {

    private ImageBarnnerFrameLayout mGroup;
    private int[] ids = new int[]{
            R.drawable.d,
            R.drawable.a,
            R.drawable.b,
            R.drawable.c
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //我们需要计算出我们当前手机的宽度
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        C.WITTH = dm.widthPixels;
        mGroup = (ImageBarnnerFrameLayout)findViewById(R.id.image_group);
        mGroup.setLisenner(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.addBitmaps(list);
       /* for (int i = 0;i < ids.length;i++){
            ImageView iv = new ImageView(this);
            iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
            iv.setLayoutParams(new ViewGroup.LayoutParams(width,ViewGroup.LayoutParams.WRAP_CONTENT));
            iv.setImageResource(ids[i]);
            mGroup.addView(iv);
        }

        mGroup.setLister(this);
        */
    }
    public void clickImageIndex(int pos){
        Toast.makeText(this,"pos = "+pos,Toast.LENGTH_SHORT).show();
    }
}

C类

public class C {
    public static int WITTH = 0;
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.example.hp.zzdlunbo7.MainActivity">

    <com.example.hp.zzdlunbo7.view.ImageBarnnerFrameLayout
        android:id="@+id/image_group"
        android:layout_width="match_parent"
        android:layout_height="200dp">
    </com.example.hp.zzdlunbo7.view.ImageBarnnerFrameLayout>

</RelativeLayout>

导包时要注意,有的包java和Android都有,出错可能就是导入了java的包

代码是根据慕课上的老师的课程写的,老师讲的很好,强烈建议大家去听,讲解的很详细,收获很大

老师课程链接https://www.imooc.com/learn/793

猜你喜欢

转载自blog.csdn.net/qq_38971487/article/details/81482433