控件用处
在很多App中,例如淘宝、京东的主界面都会有一个Banner轮播图,左右是无限循环滚动的,本人做过的多个项目中也都有轮播控件,之前思路没有打开,只想到了从Adapter上面去下功夫,然后在position的上面做计算,结果很遗憾的是并没有实现,之后在网上看了其他人的做法,都说在返回count的时候返回一个无限大的int,这个样子做呢确实是实现了功能,但是有一个问题就是只能一直往后面滑动,并不能往前面无线滑。当时也就没有太在意,但是现在又发现了一个重要的问题App在前台时间过久之后由于无限大的ViewPager会造成应用响应不流畅,所以又再重新写了一个控件。
实现思路
加入所有四张图片需要显示,那么四张图片分别对应为1、2、3、4,那么需要把最后一张添加到第一个位置,第一张也添加到最后一个位置,这时候的排序变成4、1、2、3、4、1,对应在List里面的索引则为0、1、2、3、4、5,这时候给ViewPager加监听,当当前显示的索引到0的时候调用setCurrentItem();方法把索引切换到4,当显示索引为5的时候调用setCurrentItem();方法把索引切换到1。setCurrentItem();方法的的第二个参数是Boolean类型的,表示在切换过程中是否显示滚动动画,这里设置为false。如果只是这个样子的话调用方法切换过来的时候视图会闪烁一下再显示,这里是因为视图会重新创建,所以这里需要调用一个方法来设置ViewPager的视图缓存数量。setOffscreenPageLimit(size);设置和图片列表数量一样,这里的话size对应应该是6。至此就应该进行编码工作了。
代码
里面引用了一些其他的类,有一个是获取网络图片的工具类,有一个是计算屏幕尺寸的工具类。可自行用其他方式实现相同方法即可。
/**
*
* <b>循环轮播</b><br>
* 先调用init方法初始化,再通过setBannerUrlList设置图片链接列表,最后setOnBannerClickListener设置监听事件
*
* @Company RZQC
* @author AntichristM<br>
* <b> create at </b>2016-5-20 上午10:57:41
* @Mender AntichristM<br>
* <b> change at </b>2016-5-20 上午10:57:41
*/
public class BannerPagerPlus extends RelativeLayout {
public BannerPagerPlus(Context context) {
super(context);
}
public BannerPagerPlus(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BannerPagerPlus(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private OnBannerClickListener onBannerClickListener;
public ViewPager vPager;
public LinearLayout lLayout;
/**
* 下面的几个点
*/
public List<ImageView> listImageView = new ArrayList<ImageView>();
/**
* 图片链接列表
*/
public List<String> listBannerUrl = new ArrayList<String>();
private boolean isFirst = true;
private int pagerIndex = 0;
/**
* 最先调用这个方法初始化控件
*/
public void init() {
vPager = new ViewPager(getContext());
lLayout = new LinearLayout(getContext());
LayoutParams paramsPager = new LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
LayoutParams paramsLayout = new LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
paramsLayout.addRule(ALIGN_PARENT_BOTTOM);
paramsLayout.bottomMargin = 20;
vPager.setId(0);
lLayout.setId(1);
lLayout.setGravity(Gravity.CENTER_HORIZONTAL);
vPager.setOnPageChangeListener(new MyOnPageChangeListener());
this.addView(vPager, paramsPager);
this.addView(lLayout, paramsLayout);
this.getLayoutParams().height = (int) (antiScreenUtils
.getScreenWidth(getContext()) / 1.5);
}
private ImageView getImageView() {
ImageView iView = new ImageView(getContext());
return iView;
}
/**
* 设置图片链接列表
*
* @param list
*/
public void setBannerUrlList(List<String> list) {
vPager.removeAllViews();
this.lLayout.removeAllViews();
// 清空ImageView的内存占用
for (ImageView iv : listImageView) {
iv.setImageBitmap(null);
}
listImageView.clear();
// 把最后一张加到第一张,第一张加到最后一张
listBannerUrl.clear();
listBannerUrl.add(list.get(list.size() - 1));
listBannerUrl.addAll(list);
listBannerUrl.add(list.get(0));
// 设置ViewPager页面缓存数量与图片数量相同,在第一页和最后一页切换的时候才不会闪,要极致性能可以去掉
vPager.setOffscreenPageLimit(listBannerUrl.size());
// 从第二张开始显示
pagerIndex = 1;
// 这里是圆点
LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams(
antiScreenUtils.dp2Px(getContext(), 6), antiScreenUtils.dp2Px(
getContext(), 6));
imageParams.leftMargin = 20;
// 添加圆点的时候少添加两个
listImageView.clear();
for (int i = 0; i < listBannerUrl.size() - 2; i++) {
listImageView.add(getImageView());
this.lLayout.addView(listImageView.get(i), imageParams);
}
List<ImageView> imageList = new ArrayList<ImageView>();
// 从网络获取图片并生成ImageView对象
for (int i = 0; i < listBannerUrl.size(); i++) {
String url = listBannerUrl.get(i);
ImageView iv = new ImageView(getContext());
iv.setScaleType(ScaleType.FIT_XY);
antiImageLoader.getInstance().display(url, iv);
// 确保回调方法中的索引正确
if (i > 0 && i < listBannerUrl.size() - 1) {
iv.setTag(i - 1);
}
iv.setBackgroundResource(R.drawable.bg_btn_white);
imageList.add(iv);
}
BannerAdapter adapter = new BannerAdapter(imageList);
vPager.setAdapter(adapter);
// 点从0开始
currentDot(0);
// 从1开始
vPager.setCurrentItem(1, false);
if (isFirst) {
startScrollPager();
isFirst = !isFirst;
}
}
public Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
pagerIndex++;
// 切换ViewPager的时候在监听里面在去调用方法切换点
vPager.setCurrentItem(pagerIndex);
}
};
public void startScrollPager() {
stopScrollPager();
handler.postDelayed(runnable, 3000);
}
public void stopScrollPager() {
handler.removeCallbacks(runnable);
}
/**
* 切换下面的点
*/
public void currentDot(int index) {
if (listImageView.size() == 0) {
return;
}
index = index % (listImageView.size());
for (int i = 0; i < listImageView.size(); i++) {
// 设置默认的样式
listImageView.get(i).setBackgroundResource(
R.drawable.bg_circle_banner);
}
// 设置当前位置的点的样式
listImageView.get(index).setBackgroundResource(
R.drawable.bg_circle_banner_on);
}
/**
* 页面切换的监听
*/
public class MyOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int arg0) {
if (arg0 == 0) {
// 切换到第一张了,跳转到最后一张
pagerIndex = listBannerUrl.size() - 1;
vPager.setCurrentItem(listBannerUrl.size() - 2, false);
} else if (arg0 > 0 && arg0 < listBannerUrl.size() - 1) {
// 中间
stopScrollPager();
startScrollPager();
pagerIndex = vPager.getCurrentItem();
currentDot(vPager.getCurrentItem() - 1);
} else if (arg0 == listBannerUrl.size() - 1) {
// 最后一张,跳转到第一张
pagerIndex = 1;
vPager.setCurrentItem(1, false);
} else {
// 日了狗才会调用这里
}
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
}
/**
* 绑定ViewPager
*
* @author AntichristM
*
*/
class BannerAdapter extends PagerAdapter {
private List<ImageView> listImage = new ArrayList<ImageView>();
public BannerAdapter(List<ImageView> list) {
// 给listImage生成ImageView的对象
listImage = list;
}
@Override
public int getCount() {
return listImage.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
ImageView view = listImage.get(position);
container.removeView(view);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
listImage.get(position).setOnClickListener(imageOnclick);
if (listImage.get(position).getParent() != null) {
ViewGroup vg = (ViewGroup) listImage.get(position).getParent();
vg.removeView(listImage.get(position));
}
antiImageLoader.getInstance().display(listBannerUrl.get(position),
listImage.get(position));
container.addView(listImage.get(position), 0);
return listImage.get(position);
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
}
public OnBannerClickListener getOnBannerClickListener() {
return onBannerClickListener;
}
public void setOnBannerClickListener(
OnBannerClickListener onBannerClickListener) {
this.onBannerClickListener = onBannerClickListener;
}
OnClickListener imageOnclick = new OnClickListener() {
@Override
public void onClick(View arg0) {
Integer position = (Integer) arg0.getTag();
if (onBannerClickListener != null) {
onBannerClickListener.onClick(position);
}
}
};
public interface OnBannerClickListener {
public abstract void onClick(int position);
}
}
总结
一定要善于利用mspaint这一个工具啊!
最后要说一点,就是关于init();方法的调用,我还没找到更好的方式直接在BannerPagerPlus这个类中重写某个方法来进行调用,还请大神指教。
更新
上面在做列表添加到头尾各一个的时候有个问题,没有判断传入的图片链接list的长度是否小于1,如果小于1的话,就会越界了。所以加个判断
// 把最后一张加到第一张,第一张加到最后一张
if (list.size() > 1) {
listBannerUrl.clear();
listBannerUrl.add(list.get(list.size() - 1));
listBannerUrl.addAll(list);
listBannerUrl.add(list.get(0));
} else if (list.size() == 1) {
listBannerUrl.clear();
listBannerUrl.addAll(list);
listBannerUrl.addAll(list);
listBannerUrl.addAll(list);
} else {
listBannerUrl.clear();
}
再更新
之前的处理方式在第一个页面和最后一个页面进行切换的时候会出现有顿挫感,切换的时候并没有出现滑动动画,现在这里更改一下页面切换的监听。在两个不同的方法中进行监听,这样实现的了在页面本身滑动动画进行完了之后才会进行判断切换。
/**
* 页面切换的监听
*/
public class MyOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
if(arg1!=0){
return;
}
if (arg0 == 0) {
// 切换到第一张了,跳转到最后一张
pagerIndex = listBannerUrl.size() - 1;
vPager.setCurrentItem(listBannerUrl.size() - 2, false);
} else if (arg0 > 0 && arg0 < listBannerUrl.size() - 1) {
// 中间
stopScrollPager();
startScrollPager();
pagerIndex = vPager.getCurrentItem();
} else if (arg0 == listBannerUrl.size() - 1) {
// 最后一张,跳转到第一张
pagerIndex = 1;
vPager.setCurrentItem(1, false);
} else {
// 日了狗才会调用这里
}
}
@Override
public void onPageSelected(int arg0) {
if (arg0 == 0) {
// 第一张,显示最后一张
currentDot(listImageView.size() - 1);
} else if (arg0 > 0 && arg0 < listBannerUrl.size() - 1) {
// 中间
currentDot(vPager.getCurrentItem() - 1);
} else if (arg0 == listBannerUrl.size() - 1) {
// 最后一张,显示第一张
currentDot(0);
} else {
// 日了狗才会调用这里
}
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
}
还要更新
昨天晚上突然发现在第一张图片和最后一张图片切换的时候进行点击,就会造成Crash,发现是在点击监听的时候getTag()方法返回了空值,最后定位问题是在setTag()的时候有两种情况没有考虑到,所以在setTag()这里现在要加两个判断,写成这个样子就没得问题了。
if (i > 0 && i < listBannerUrl.size() - 1) {
iv.setTag(i - 1);
} else if (i == 0) {
iv.setTag(listBannerUrl.size() - 3);
} else {
iv.setTag(0);
}