Android 自定义一个轮播图

有限空间内展示更多的内容,轮播图是个不错的选择,本文将实现一个轮播图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>




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值