Android实例——轮播图

效果图

可实现自动轮播,且标题和指示器(红点)随图片切换
在这里插入图片描述

attrs.xml

res/value下创建attrs.xml,实现自定义属性,用于控制控件中标题是否显示、显示几张图片(未实现)、轮播时间

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Looper_style">
        <attr name="is_title_show" format="boolean" />
        <attr name="show_pager_count" format="enum">
            <enum name="single" value="1" />
            <enum name="multi" value="3" />
        </attr>
        <attr name="switch_time" format="integer"/>
    </declare-styleable>
</resources>

activity_main.xml

主页布局使用了自定义控件LooperPager,且声明了xmlns:my命名空间和3个额外属性

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:my="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=".MainActivity">

    <com.example.demo14_looperdemo.view.LooperPager
        android:id="@+id/sp_loop_pager"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        my:show_pager_count="multi"
        my:is_title_show="true"
        my:switch_time="4000" />
</RelativeLayout>

looppager.xml

looppager.xml为自定义LinearLayout的布局文件

  • 由自定义控件MyViewPager、TextView、横向LinearLayout组成,分别用于显示轮播图、标题和指示器
  • clipChildren=false 使得子控件可以超出父控件的范围,再设置左右margin使得同时显示3张图片
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:orientation="vertical">
    <com.example.demo14_looperdemo.MyViewPager
        android:id="@+id/looper_pager_vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="40dp"
        android:layout_marginRight="40dp"
        android:clipChildren="false" />

    <TextView
        android:id="@+id/looper_title_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#66ffffff"
        android:paddingLeft="10dp"
        android:paddingTop="2dp"
        android:paddingRight="10dp"
        android:paddingBottom="2dp"
        android:text="标题"
        android:textAlignment="center"
        android:gravity="center_horizontal" />

    <LinearLayout
        android:id="@+id/looper_point_container_ll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="5dp"
        android:orientation="horizontal">

    </LinearLayout>
</RelativeLayout>

MyViewPager.java

MyViewPager为自定义ViewPager

  • setCurrentItem(Integer.MAX_VALUE / 2 + 1)实现向左滑动
  • startLooper()调用postDelayed()每隔一段时间让currentItem++,即自动轮播,view自带postDelayed()方法无需创建handler
  • setOnTouchListener()设置触摸和点击事件,点击时可能导致触摸事件(触摸时不轮播)被消费掉,故需要区分二者(根据移动距离判断是点击还是触摸、根据点击时长判断是短点击还是长点击)
public class MyViewPager extends ViewPager {

    private static final int DEFAULT_SWITCH_TIME = 1000;
    private long delayTime = DEFAULT_SWITCH_TIME;

    public MyViewPager(@NonNull Context context) {
        this(context, null);
    }

    private boolean isClick = false;
    private float downX;
    private float downY;
    private long downTime;
    private OnViewPagerClickListener mItemClickListener = null;

    public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int action = motionEvent.getAction();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        downX = motionEvent.getX();
                        downY = motionEvent.getY();
                        downTime = System.currentTimeMillis();
                        stopLooper();
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        float dx = Math.abs(motionEvent.getX() - downX);
                        float dy = Math.abs(motionEvent.getY() - downY);
                        long dTime = System.currentTimeMillis() - downTime;
                        isClick = dy <= 5 && dx <= 5 && dTime <= 1000;
                        //当点击时,消费了点击事件,导致触摸事件(即触摸时不轮播功能)失效
                        //通过判断点击前后的x,y的移动范围(<=5)则是点击事件,否则为触摸事件
                        //若点击事件小于1s,则是onclick,否则是LongClick
                        if (isClick && mItemClickListener != null) {
                            mItemClickListener.onViewPagerClick(getCurrentItem());
                        }
                        startLooper();
                        break;
                }
                return false;
            }
        });
    }

    public void setViewPagerClickListener(OnViewPagerClickListener itemClickListener) {
        mItemClickListener = itemClickListener;
    }

    public interface OnViewPagerClickListener {
        void onViewPagerClick(int position);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        setCurrentItem(Integer.MAX_VALUE / 2 + 1);
        startLooper();
    }

    private void startLooper() {
        removeCallbacks(mTask);
        //这里没调用Handler,view里面自带postDelayed
        postDelayed(mTask, delayTime);
    }

    private Runnable mTask = new Runnable() {
        @Override
        public void run() {
            int currentItem = getCurrentItem();
            currentItem++;
            setCurrentItem(currentItem);
            postDelayed(this, delayTime);
        }
    };

    public void setDelayTime(long delayTime) {
        this.delayTime = delayTime;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopLooper();
    }

    private void stopLooper() {
        removeCallbacks(mTask);
    }
}

SizeUtils.java

实现dp到px的转换

public class SizeUtils {
    public static int dip2px(Context context, float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

LooperPager.java

LooperPager加载looppager.xml布局

  • 构造函数统一入口加载布局
  • initAttributes()读取xml文件中的自定义属性
  • setOffscreenPageLimit()预设ViewPager的图片、避免出现背景色
  • setViewPagerClickListener()回调MyViewPager的onViewPagerClick()获取点击位置,算出相对位置再回调到MainActivity中修改UI
  • addOnPageChangeListener()中的onPageSelected()当轮播图被选中时调用,更新标题和修改指示器,即让两者跟着轮播切换
  • InnerAdapter类供外部实现,getDataSize()返回实现轮播数量,因为getCount()要返回Integer.MAX_VALUE实现无限轮播;getSubView()返回要添加的图片布局
public class LooperPager extends LinearLayout {

    private MyViewPager mViewPager;
    private LinearLayout mPointContainer;
    private TextView mTextView;
    private OnPagerChangeListener mPagerChangeListener = null;
    private InnerAdapter mInnerAdapter;
    private onLooperPagerClickListener mOnLooperPagerClickListener = null;
    private boolean mIsTitleShow;
    private int mPagerShowCount;
    private int mSwitchTime;

    public LooperPager(Context context) {
        this(context, null);
    }

    public LooperPager(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LooperPager(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.looppager, this, true);
        //等价以下两行代码
        /*View view = LayoutInflater.from(context).inflate(R.layout.looppager, this, false);
        addView(view);*/
        initAttributes(context, attrs);
        initView();
        initData();
        initListener();
    }

    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Looper_style);
        mIsTitleShow = ta.getBoolean(R.styleable.Looper_style_is_title_show, true);
        mPagerShowCount = ta.getInteger(R.styleable.Looper_style_show_pager_count, 1);
        mSwitchTime = ta.getInteger(R.styleable.Looper_style_switch_time, -1);
        ta.recycle();
    }

    private void initView() {
        mViewPager = this.findViewById(R.id.looper_pager_vp);
        mPointContainer = this.findViewById(R.id.looper_point_container_ll);
        mTextView = this.findViewById(R.id.looper_title_tv);
        if (!mIsTitleShow) {
            mTextView.setVisibility(GONE);
        }
    }

    private void initData() {
        //setOffscreenPageLimit让ViewPager预先加载,避免出现背景色
        mViewPager.setOffscreenPageLimit(3);
        //setPageMargin为ViewPager的item之间添加边距
        mViewPager.setPageMargin(SizeUtils.dip2px(getContext(), 10));
        if (mSwitchTime != -1) {
            mViewPager.setDelayTime(mSwitchTime);
        }
    }

    private void initListener() {
        mViewPager.setViewPagerClickListener(new MyViewPager.OnViewPagerClickListener() {
            @Override
            public void onViewPagerClick(int position) {
                if (mOnLooperPagerClickListener != null && mInnerAdapter != null) {
                    int realPosition = position % mInnerAdapter.getDataSize();
                    mOnLooperPagerClickListener.onLooperPagerClick(realPosition);
                }
            }
        });
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                if (mPagerChangeListener != null && mInnerAdapter != null) {
                    int realPosition = position % mInnerAdapter.getDataSize();
                    mTextView.setText(mPagerChangeListener.getTitle(realPosition));
                    updateIndicator(realPosition);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }


    public interface OnPagerChangeListener {
        String getTitle(int position);
    }

    public void setPagerChangeListener(OnPagerChangeListener listener) {
        this.mPagerChangeListener = listener;
    }

    public void setAdapter(InnerAdapter innerAdapter) {
        this.mInnerAdapter = innerAdapter;
        mViewPager.setAdapter(innerAdapter);
    }

    public abstract static class InnerAdapter extends PagerAdapter {

        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }

        @Override
        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
            return view == object;
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            container.removeView((View) object);
        }

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, final int position) {
            final int realPosition = position % getDataSize();
            View itemView = getSubView(container, realPosition);
            container.addView(itemView);
            return itemView;
        }

        protected abstract int getDataSize();

        public abstract View getSubView(ViewGroup container, int realPosition);
    }

    public void setOnLooperPagerClickListener(onLooperPagerClickListener listener) {
        this.mOnLooperPagerClickListener = listener;
    }

    public interface onLooperPagerClickListener {
        void onLooperPagerClick(int position);
    }

    private void updateIndicator(int realPosition) {
        if (mInnerAdapter != null) {
            mPointContainer.removeAllViews();
            for (int i = 0; i < mInnerAdapter.getDataSize(); i++) {
                View point = new View(getContext());
                if (realPosition == i) {
                    point.setBackgroundColor(Color.parseColor("#ff0000"));
                } else {
                    point.setBackgroundColor(Color.parseColor("#ffffff"));
                }
                LayoutParams layoutParams = new LayoutParams(SizeUtils.dip2px(getContext(), 5), SizeUtils.dip2px(getContext(), 5));
                layoutParams.setMargins(SizeUtils.dip2px(getContext(), 5), 0, SizeUtils.dip2px(getContext(), 5), 0);
                point.setLayoutParams(layoutParams);
                mPointContainer.addView(point);
            }
        }
    }
}

ViewPagerItem.java

该类为轮播数据的bean类

public class ViewPagerItem {
    private String title;
    private Integer picResId;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Integer getPicResId() {
        return picResId;
    }

    public void setPicResId(Integer picResId) {
        this.picResId = picResId;
    }

    public ViewPagerItem(String title, Integer picResId) {
        this.title = title;
        this.picResId = picResId;
    }
}

LooperPagerAdapter.java

LooperPagerAdapter是LooperPager的适配器

  • 实现getDataSize()返回实际大小
  • 实现getSubView()返回待轮播的图片
public class LooperPagerAdapter extends LooperPager.InnerAdapter {


    private final List<ViewPagerItem> mData;

    public LooperPagerAdapter(List<ViewPagerItem> data) {
        mData = data;
    }

    @Override
    protected int getDataSize() {
        return mData.size();
    }

    @Override
    public View getSubView(ViewGroup container, int position) {
        ImageView iv = new ImageView(container.getContext());
        iv.setImageResource(mData.get(position).getPicResId());
        iv.setScaleType(ImageView.ScaleType.FIT_XY);
        return iv;
    }
}

MainActivity.java

MainActivity作为自定义控件的使用者,找到LooperPager

  • 设置带数据的适配器
  • 设置点击事件,根据点击位置修改UI
  • 设置轮播标题,根据轮播位置设置标题数据
public class MainActivity extends AppCompatActivity {

    private LooperPager mLooperPager;
    private static final List<ViewPagerItem> mData = new ArrayList<>();

    static {
        mData.add(new ViewPagerItem("第一个图片", R.mipmap.pic1));
        mData.add(new ViewPagerItem("第二个图片", R.mipmap.pic2));
        mData.add(new ViewPagerItem("第三个图片", R.mipmap.pic3));
        mData.add(new ViewPagerItem("第四个图片", R.mipmap.pic4));
    }

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

    private void initView() {
        mLooperPager = this.findViewById(R.id.sp_loop_pager);
    }

    private void initData() {
        mLooperPager.setAdapter(new LooperPagerAdapter(mData));
        mLooperPager.setOnLooperPagerClickListener(new LooperPager.onLooperPagerClickListener() {
            @Override
            public void onLooperPagerClick(int position) {
                Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "个item", Toast.LENGTH_SHORT).show();
            }
        });
        mLooperPager.setPagerChangeListener(new LooperPager.OnPagerChangeListener() {
            @Override
            public String getTitle(int position) {
                return mData.get(position).getTitle();
            }
        });
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值