自己造轮子--一款实用的Android广告栏实现过程(二)
这一篇是接着自己造轮子--一款实用的Android广告栏实现过程(一)写的,没看过的先看上一篇,在上一篇中我提到对轮子的要求,也分析了部分关键实现代码(轮播,修复原生切换速度过快问题,自动切换),今天分析剩余的部分,github地址BannerLayoutDemo。
bannerLayoutDemo.gif添加指示器--绘制指示器样式
github上也有各种各样很棒的指示器,可作为独立控件,这里我先简单处理直接集成到内部,后期有需求再进行重构,最简单的指示器是用两张不同状态的小图片做的,但我认为这样做相对实现是简单的,但对于修改却显得有些麻烦,适配也是问题,简单修改何必大动干戈呢?
这里借鉴了daimajia的思路,指示器是用代码绘出来的,怎么绘呢?看代码,以选中的状态为例,绘制一次便可,将drawable存起来使用:
Drawable selectedDrawable;
GradientDrawable selectedGradientDrawable = new GradientDrawable();
//设置指示器的颜色
selectedGradientDrawable.setColor(selectedIndicatorColor);
//设置指示器的形状
selectedGradientDrawable.setShape(GradientDrawable.RECTANGLE);
//设置指示器的大小
selectedGradientDrawable.setSize(selectedIndicatorWidth, selectedIndicatorHeight);
selectedLayerDrawable = new LayerDrawable(new Drawable[]{selectedGradientDrawable});
selectedDrawable = selectedLayerDrawable;
这样指示器的形状,大小,颜色可以随意换了,支持换肤也更容易
添加指示器--位置
大家都知道在xml文件中使用RelativeLayout父布局可以控制子布局的位置,用代码怎么去做呢?首先,BannerLayout是继承与RelativeLayout的,看代码
RelativeLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
switch (indicatorPosition) {
case centerBottom://下中
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
break;
case rightBottom://右下
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
break;
}
//添加指示器容器布局到BannerLayout
addView(indicatorContainer, params);
两个属性必须分开添加不能写成
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT|RelativeLayout.ALIGN_PARENT_BOTTOM);
如何设置指示器margin和padding这里就不再多说
切换指示器
我之前的想法是这样的,不用循环做,只需知道上一个选中的页面和即将要跳转的页面位置即可,实现思路是这样的
private void switchIndicator(int currentPosition) {
if (oldPosition != -1){
((ImageView)indicatorContainer.getChildAt(oldPosition)).setImageDrawable(unSelectedDrawable);
}
((ImageView)indicatorContainer.getChildAt(currentPosition)).setImageDrawable(selectedDrawable);
oldPosition = currentPosition;
}~~~
但实际的运行效果却可能出现错乱的现象,不知是哪里出了问题,目前就采用了循环来做,后期可能会改进,这样做虽然简单,但效率始终不高
private void switchIndicator(int currentPosition) {
for (int i = 0; i < indicatorContainer.getChildCount(); i++) {
((ImageView) indicatorContainer.getChildAt(i)).setImageDrawable(i == currentPosition ? selectedDrawable : unSelectedDrawable);
}
}~~~
添加页面点击监听回调
用法就像ListView的setOnItemClickListener一样,来看看代码如何实现
private OnBannerItemClickListener onBannerItemClickListener;
//给每个页面添加点击事件
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onBannerItemClickListener != null) {
//不直接处理点击事件,转交给onBannerItemClickListener
onBannerItemClickListener.onItemClick(position);
}
}
});
public void setOnBannerItemClickListener(OnBannerItemClickListener onBannerItemClickListener) {
this.onBannerItemClickListener = onBannerItemClickListener;
}
public interface OnBannerItemClickListener {
void onItemClick(int position);
}~~~
使用
bannerLayout.setOnBannerItemClickListener(new BannerLayout.OnBannerItemClickListener() {
@Override
public void onItemClick(int position) {
//处理点击事件
}
});
####遇到的一些坑
上篇讲到了[BGABanner-Android](https://github.com/bingoogolapple/BGABanner-Android),部分思想也是参考这个库的,例如指示器位置的处理方案,但我要说的坑也在这里,低于3张图片就会直接抛异常,我试着将异常不抛出看看,一张或者两张图片的时候切换效果惨不忍睹(我猜想和ViewPager的懒加载机制有关),所以作者处理为直接抛异常,但这样不行啊,需求不可控制啊,必须解决这个问题,不然就像定时炸弹。
问题解决思路:既然轮播是伪的,图片的张数也可以是伪的,只需要给用户看起来是那样就行了,1张也可以是3x1张相同的图片,2张也可以是2x2张相同图片,3张及以上没问题就无需处理,关键代码
//添加本地图片路径
public void setViewRes(List<Integer> viewRes) {
List<View> views = new ArrayList<>();
itemCount = viewRes.size();
//主要是解决当item为小于3个的时候滑动有问题,这里将其拼凑成3个以上
if (itemCount < 1) {//当item个数0
throw new IllegalStateException("item count not equal zero");
} else if (itemCount < 2) {//当item个数为1
views.add(getImageView(viewRes.get(0), 0));
views.add(getImageView(viewRes.get(0), 0));
views.add(getImageView(viewRes.get(0), 0));
} else if (itemCount < 3) {//当item个数为2
views.add(getImageView(viewRes.get(0), 0));
views.add(getImageView(viewRes.get(1), 1));
views.add(getImageView(viewRes.get(0), 0));
views.add(getImageView(viewRes.get(1), 1));
} else {
for (int i = 0; i < viewRes.size(); i++) {
views.add(getImageView(viewRes.get(i), i));
}
}
setViews(views);
}~~~
添加网络地址和本地的思路差不多,使用的是Glide来处理加载网络图片,还有就是添加各种自定义属性。
github地址BannerLayoutDemo。
完整代码实现请看源码,欢迎fork和star以及提出你宝贵的意见