有限空间内展示更多的内容,轮播图是个不错的选择,本文将实现一个轮播图SlideShowView,效果如下图所示:
因为轮播图的每一个页面都有文字和图片,为了整合图片和文字,SlideShowView选择继承Fraement
轮播图的主要属性如下:public class SlideShowView extends FrameLayout
/*存储图片链接*/ private String[] imageUrls; /*存储文字内容*/ private String[] contents; /*ViewPager装图片和文字*/ private ViewPager viewPager; /*右下边起到指示作用的小圆点*/ private List<View> dotViewsList; /*定时功能,自动轮播*/ private ScheduledExecutorService scheduledExecutorService; /*记录轮播下标*/ private int currentItem = 0;
轮播图要求定时更换展示的内容,定时功能使用ScheduledExecutorService实现
/*开启轮播*/ public void startPlay(){ scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(new SlideShowTask(), 1, 5, TimeUnit.SECONDS); }定时时间到的话将会执行SlideShowTask任务,该任务主要是切换页面,这里更换下标即可,子线程不能更新UI,所以使用handler将消息发给主线程
private class SlideShowTask implements Runnable { @Override public void run() { synchronized (viewPager) { currentItem = (currentItem+1)%imageUrls.length; handler.obtainMessage().sendToTarget(); } } }handler在主线程中接受换页消息,执行换页操作。为了避免handler内存泄露,将内部handler定义为静态的,保持一个外部类的弱引用
private Handler handler = new SafeHandler(this); static class SafeHandler extends Handler { WeakReference<SlideShowView> mOuter; SafeHandler(SlideShowView v) { mOuter= new WeakReference<SlideShowView>(v); } @Override public void handleMessage(Message msg) { final SlideShowView v = mOuter.get(); super.handleMessage(msg); if (v != null) { v.viewPager.setCurrentItem(v.currentItem); } } }由于开启了线程池和handler,为了防止生命周期问题导致的内存泄露,在轮播图离开视图被销毁的时候要关闭线程池并且清除handler消息回调
/*关闭轮播*/ public void stopPlay() { if(scheduledExecutorService!=null){ scheduledExecutorService.shutdown(); scheduledExecutorService=null; } }
@Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); stopPlay(); handler.removeCallbacksAndMessages(null); }为了加载图片,使用Picasso框架,根据url加载图片并显示在控件上
Picasso.with(context) .load(imageUrls[position]) .placeholder(R.mipmap.slide_show_view_loading) .error(R.mipmap.slide_show_view_loading) .config(Bitmap.Config.RGB_565) /*.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)*/ .into(image);右下角小红点指示器是一个LinearLayout,根据页面数量动态生成
/*小红点指示标志处理*/ for (int i = 0; i < imageUrls.length; i++) { ImageView dotView = new ImageView(context); dotView.setId(i); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.leftMargin = 4; params.rightMargin = 4; dotLayout.addView(dotView, params); dotViewsList.add(dotView); } ImageView[] dotView = new ImageView[imageUrls.length]; for(int i = 0;i<imageUrls.length;i++){ dotView[i]= (ImageView) findViewById(i); dotView[i].setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { viewPager.setCurrentItem(v.getId()); } }); }ViewPager监听界面滑动,自己实现一个 ViewPager.OnPageChangeListener即可
完整代码如下:
public class SlideShowView extends FrameLayout{
private static final String TAG = "SlideShowView";
private Context context;
/*存储图片链接*/
private String[] imageUrls;
/*存储文字内容*/
private String[] contents;
/*ViewPager装图片和文字*/
private ViewPager viewPager;
/*右下边起到指示作用的小圆点*/
private List<View> dotViewsList;
/*定时功能,自动轮播*/
private ScheduledExecutorService scheduledExecutorService;
/*记录轮播下标*/
private int currentItem = 0;
public SlideShowView(Context context) {
this(context, null);
}
public SlideShowView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlideShowView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
/*传入图片链接和文字内容*/
public void initData(String[] imageUrls,String[] contents) {
this.imageUrls = imageUrls;
this.contents = contents;
dotViewsList = new ArrayList<>();
initUI(context);
}
/*开启轮播*/
public void startPlay(){
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(new SlideShowTask(), 1, 5, TimeUnit.SECONDS);
}
/**
* 发现使用这个轮播图的地方没有关闭线程池,导致内存严重泄露
* by:wangshihui
* at:2016/4/25 0025 10:29
*/
/*关闭轮播*/
public void stopPlay() {
if(scheduledExecutorService!=null){
scheduledExecutorService.shutdown();
scheduledExecutorService=null;
}
}
/*视图销毁释放资源
* This is called when the view is detached from a window.*/
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopPlay();
handler.removeCallbacksAndMessages(null);
}
/**
* Handler的正确用法
* by:wangshihui
* at:2016/4/28 0028 10:47
*/
/*使用Handler注意内存泄露的问题*/
private Handler handler = new SafeHandler(this);
static class SafeHandler extends Handler {
WeakReference<SlideShowView> mOuter;
SafeHandler(SlideShowView v) {
mOuter= new WeakReference<SlideShowView>(v);
}
@Override
public void handleMessage(Message msg) {
final SlideShowView v = mOuter.get();
super.handleMessage(msg);
if (v != null) {
v.viewPager.setCurrentItem(v.currentItem);
}
}
}
/**
*执行轮播图切换任务
*/
private class SlideShowTask implements Runnable {
@Override
public void run() {
synchronized (viewPager) {
currentItem = (currentItem+1)%imageUrls.length;
handler.obtainMessage().sendToTarget();
}
}
}
/**
* ViewPager的监听器
* 当ViewPager中页面的状态发生改变时调用
*/
private class ViewPagerPageChangeListener implements ViewPager.OnPageChangeListener {
boolean isPlaying = false;
@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
/*正在滑动或拖动*/
case 1:
isPlaying = false;
break;
/*滑动结束*/
case 2:
isPlaying = true;
break;
/*什么都没做*/
case 0:
/*滑动到头尾后循环滑动处理*/
if (viewPager.getCurrentItem() == viewPager.getAdapter().getCount() - 1 && !isPlaying) {
viewPager.setCurrentItem(0);
}
else if (viewPager.getCurrentItem() == 0 && !isPlaying) {
viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 1);
}
break;
}
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int pos) {
currentItem = pos;
/*设置下边的小圆点指示标志*/
for(int i=0;i < dotViewsList.size();i++){
if(i == pos){
dotViewsList.get(pos).setBackgroundResource(R.mipmap.dot_focus);
}else {
dotViewsList.get(i).setBackgroundResource(R.mipmap.dot_blur);
}
}
}
}
private void initUI(final Context context) {
View view = LayoutInflater.from(context).inflate(R.layout.viewpager_with_net, this,true);
LinearLayout dotLayout = (LinearLayout)findViewById(R.id.dotLayout);
/*手动播放时上一页下一页按钮*/
ImageView front = (ImageView) view.findViewById(R.id.back);
ImageView next = (ImageView) view.findViewById(R.id.more);
front.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(currentItem>0){
currentItem = (currentItem-1)%imageUrls.length;
handler.obtainMessage().sendToTarget();
}else{
currentItem =imageUrls.length-1;
handler.obtainMessage().sendToTarget();
}
}
});
next.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(currentItem<imageUrls.length){
currentItem = (currentItem+1)%imageUrls.length;
handler.obtainMessage().sendToTarget();
}
}
});
/*小红点指示标志处理*/
for (int i = 0; i < imageUrls.length; i++) {
ImageView dotView = new ImageView(context);
dotView.setId(i);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.leftMargin = 4;
params.rightMargin = 4;
dotLayout.addView(dotView, params);
dotViewsList.add(dotView);
}
ImageView[] dotView = new ImageView[imageUrls.length];
for(int i = 0;i<imageUrls.length;i++){
dotView[i]= (ImageView) findViewById(i);
dotView[i].setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewPager.setCurrentItem(v.getId());
}
});
}
viewPager = (ViewPager) view.findViewById(R.id.view);
viewPager.setAdapter(new ViewPagerAdapter());
viewPager.setOnPageChangeListener(new ViewPagerPageChangeListener());
}
private class ViewPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return imageUrls.length;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view ==object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = LayoutInflater.from(context).inflate(R.layout.viewpager_item, null);
TextView content = (TextView) view.findViewById(R.id.content);
ImageView image = (ImageView) view.findViewById(R.id.imageview);
content.setText(contents[position]);
Logger.d(imageUrls[position]);
/*查看大图放弃memory cache
在查看大图时放弃使用内存缓存,图片从网络下载完成后会缓存到磁盘中
加载会从磁盘中加载,这样可以加速内存的回收。
其中memoryPolicy的NO_CACHE是指图片加载时放弃在内存缓存中查找,
NO_STORE是指图片加载完不缓存在内存中。*/
Picasso.with(context)
.load(imageUrls[position])
.placeholder(R.mipmap.slide_show_view_loading)
.error(R.mipmap.slide_show_view_loading)
.config(Bitmap.Config.RGB_565)
/*.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)*/
.into(image);
container.addView(view);
return view;
}
@Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/back"
android:layout_gravity="left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_alignParentLeft="true"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:src="@mipmap/backa"/>
<ImageView
android:layout_gravity="right"
android:id="@+id/more"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_alignParentRight="true"
android:paddingRight="5dp"
android:paddingLeft="5dp"
android:src="@mipmap/nexta"/>
<LinearLayout android:id="@+id/dotLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="bottom|right"
android:gravity="right"
android:orientation="horizontal">
</LinearLayout>
</FrameLayout>