微信公众号

Android 轮播图从 0 到 1

漏洞百出

轮播图是 Android 常用功能之一,效果大概是这样的:

之前我封装写了一个,基本达到了要求,是继承了 Fragment(当时脑袋肯定锈掉了),里面 Viewpager add Fragment,这次项目多处有轮播图,发现之前封装的不够用,简直漏洞百出:
1、比如底部 point 的位置,之前固定在中间,现在可能要放在右下角,point 最好也能动态改图片;
2、现在项目跟微信一样,底部 tab 切换,中间是 Fragment 替换,发现轮播图有问题,Fragment A 循环的 point 的 positoin 居然影响到了 Fragment B,照理,这是两个 BannerFragment,不会影响的啊,报以下错误:

1
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged!

经过排查,找到了原因,因为 Viewpager add Fragment 我全部放在一个类,因此:

1
public static List<Object> bannerList = new ArrayList<>();

这里 static 坏事了,之前一个 banner 没有暴露出来。
3、继承了 Fragment,引用比较麻烦,Fragment 有两者引用方法,xml 和代码,两者方式 addData 却报错;
4、banner 没有写点击回调。

再次封装

综合以上问题,我进行了优化,继承 LinearLayout,当一个控件来引用,省去不必要的麻烦,底部 point 的位置可以设置:

1
pointLayout.setGravity(bannerPointGravity);

另外自定义了属性,动态设置 point 大小和图片,轮播图循环时间,也能代码设置,完整代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/**
* Created by WuXiaolong on 2017/8/24.
* 个人博客:http://wuxiaolong.me
*/
public class BannerLayout extends LinearLayout {
private ViewPager bannerViewPager;
private LinearLayout bannerPointLayout;
private int mPosition = 0;
private int mBannerCount = 1;
private Context context;
private int bannerPointSize;
private int bannerPointGravity;
private int bannerPointDrawableSelected, bannerPointDrawableUnselected;
private int bannerDelaySecond;
private OnBannerClickListener onBannerClickListener;
private Handler handler = new Handler();
private Runnable runnable;
public BannerLayout(Context context) {
this(context, null);
}
public BannerLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
public BannerLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
this.context = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BannerLayout);
bannerPointSize = typedArray.getDimensionPixelSize(R.styleable.BannerLayout_bannerPointSize, 10);
bannerPointGravity = typedArray.getInt(R.styleable.BannerLayout_bannerPointGravity, Gravity.CENTER);
bannerDelaySecond = typedArray.getInt(R.styleable.BannerLayout_bannerDelaySecond, 3);
bannerPointDrawableSelected = typedArray.getResourceId(R.styleable.BannerLayout_bannerPointDrawableSelected, R.mipmap.point01);
bannerPointDrawableUnselected = typedArray.getResourceId(R.styleable.BannerLayout_bannerPointDrawableUnselected, R.mipmap.point02);
typedArray.recycle();
View view = View.inflate(context, R.layout.custom_banner_layout, null);
addView(view);
bannerViewPager = (ViewPager) view.findViewById(R.id.bannerViewPager);
bannerPointLayout = (LinearLayout) view.findViewById(R.id.bannerPointLayout);
bannerPointLayout.setGravity(bannerPointGravity);
}
public void start(List<Object> bannerList) {
bannerShutdown();
mBannerCount = bannerList.size();
BannerPagerAdapter bannerPagerAdapter = new BannerPagerAdapter(context, bannerList);
bannerViewPager.setAdapter(bannerPagerAdapter);
bannerViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
addPointLayout(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == 1) {
//手动拖动,移除定时
bannerShutdown();
} else {
bannerStart();
}
}
});
addPointLayout(0);
bannerStart();
}
private void addPointLayout(int position) {
if (mBannerCount == 1) {
bannerPointLayout.setVisibility(GONE);
} else {
bannerPointLayout.setVisibility(VISIBLE);
bannerPointLayout.removeAllViews();
for (int i = 0; i < mBannerCount; i++) {
ImageView imageView = new ImageView(context);
LayoutParams layoutParams = new LayoutParams(bannerPointSize, bannerPointSize);
layoutParams.setMargins(10, 0, 0, 0);
imageView.setLayoutParams(layoutParams);
if (position == i) {
imageView.setImageResource(bannerPointDrawableSelected);
} else {
imageView.setImageResource(bannerPointDrawableUnselected);
}
bannerPointLayout.addView(imageView);
}
}
}
public void bannerStart() {
if (runnable == null) {
runnable = new Runnable() {
@Override
public void run() {
mPosition = bannerViewPager.getCurrentItem();
if (mPosition < mBannerCount - 1) {
mPosition++;
} else {
mPosition = 0;
}
bannerViewPager.setCurrentItem(mPosition);
handler.postDelayed(this, bannerDelaySecond * 1000);
}
};
handler.postDelayed(runnable, bannerDelaySecond * 1000);
}
}
public void bannerShutdown() {
if (handler != null && runnable != null) {
handler.removeCallbacks(runnable);
runnable = null;
}
}
private class BannerPagerAdapter extends PagerAdapter {
private List<Object> bannerList = new ArrayList<>();
private Context context;
BannerPagerAdapter(Context context, List<Object> bannerList) {
this.context = context;
this.bannerList.clear();
this.bannerList.addAll(bannerList);
}
@Override
public int getCount() {
return bannerList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Object object = bannerList.get(position);
ImageLoaderUtil.load(context, object, R.mipmap.horizontal_default, imageView);
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onBannerClickListener != null) {
onBannerClickListener.onBannerClick(position);
}
}
});
container.addView(imageView, layoutParams);
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
public interface OnBannerClickListener {
void onBannerClick(int position);
}
public int dp2px(float var0) {
float var1 = context.getResources().getDisplayMetrics().density;
return (int) (var0 * var1 + 0.5F);
}
public BannerLayout setBannerPointSize(int bannerPointSize) {
this.bannerPointSize = dp2px(bannerPointSize);
return this;
}
public BannerLayout setBannerPointGravity(int bannerPointGravity) {
this.bannerPointGravity = bannerPointGravity;
bannerPointLayout.setGravity(bannerPointGravity);
return this;
}
public BannerLayout setBannerPointDrawableSelected(int bannerPointDrawableSelected) {
this.bannerPointDrawableSelected = bannerPointDrawableSelected;
return this;
}
public BannerLayout setBannerPointDrawableUnselected(int bannerPointDrawableUnselected) {
this.bannerPointDrawableUnselected = bannerPointDrawableUnselected;
return this;
}
public BannerLayout setBannerDelaySecond(int bannerDelaySecond) {
this.bannerDelaySecond = bannerDelaySecond;
return this;
}
public BannerLayout setOnBannerClickListener(OnBannerClickListener onBannerClickListener) {
this.onBannerClickListener = onBannerClickListener;
return this;
}
}

其中自定义属性的attrs.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BannerLayout">
<!--轮播图点的大小-->
<attr name="bannerPointSize" format="dimension" />
<!--轮播图点的位置,分别有左中右-->
<attr name="bannerPointGravity" format="enum">
<enum name="left" value="3" />
<enum name="center" value="17" />
<enum name="right" value="5" />
</attr>
<!--轮播图点选中的图片-->
<attr name="bannerPointDrawableSelected" format="reference" />
<!--轮播图点未选中的图片-->
<attr name="bannerPointDrawableUnselected" format="reference" />
<!--轮播图循环时间,单位秒-->
<attr name="bannerDelaySecond" format="integer" />
</declare-styleable>
</resources>

使用说明

xml

1
2
3
4
5
6
7
8
9
<com.wuxiaolong.bannersample.BannerLayout
android:id="@+id/bannerView"
android:layout_width="match_parent"
android:layout_height="198dp"
app:bannerDelaySecond="3"
app:bannerPointDrawableSelected="@drawable/gray_radius"
app:bannerPointDrawableUnselected="@drawable/white_radius"
app:bannerPointGravity="right"
app:bannerPointSize="10dp" />

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainActivity extends AppCompatActivity {
private BannerLayout bannerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bannerView = (BannerLayout) findViewById(R.id.bannerView);
List<Object> bannerList = new ArrayList<>();
bannerList.add(R.mipmap.horizontal_default);
bannerList.add("http://pic1.win4000.com/wallpaper/5/598161750eddb.jpg");
bannerList.add("http://pic1.win4000.com/wallpaper/4/597efb5b6aae8.jpg");
bannerView.setBannerPointSize(10);
bannerView.setBannerPointGravity(Gravity.CENTER);
bannerView.setBannerPointDrawableSelected(R.drawable.gray_radius);
bannerView.setBannerPointDrawableUnselected(R.mipmap.point01);
bannerView.setBannerDelaySecond(5);
//banner 设置方法完毕时最后调用 start 方法
bannerView.start(bannerList);
}
@Override
protected void onStop() {
super.onStop();
bannerView.bannerShutdown();
}
}

最后

如果以上还满足不了你的需求,可以使用 GitHub上的库 banner,它丰富了 pointLayout,可能是文字,它还依赖了ViewPagerTransforms,因此 ViewPager 切换有各种炫酷效果,不过我觉得有些多余,主要还是轮播。



联系作者

我的微信公众号:吴小龙同学,欢迎关注交流~