带标题的轮播图
效果图
可实现自动轮播,且标题和指示器(红点)随图片切换
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();
}
});
}
}