自定义轮播组件PictureCarousel的实现过程

定义了PictureCarousel类,直接在项目中使用,很方便集成轮播组件。
Github地址:https://github.com/bendeng/PictureCarousel
想起两年前使用Gallery来做轮播组件,当时时不时还要OOM一下,没有使用UIL时,不知内存管理的重要性,没熟练使用ViewPager时,也不知ViewPager的机制对内存的占用是多么的友好。效果图如下:
picture_carousel

实现思路

1、定义PictureCarousel类,继承RelativeLayout
2、使用ViewPager实现左右滑动
3、使用Timer实现定时滑动
4、滑动时切换图片描述和圆点焦点
5、后续优化

实现过程

定义PictureCarousel类

PictureCarousel继承RelativeLayout,因为在XML中使用,所以覆盖public PictureCarousel(Context context, AttributeSet attrs) 的构造方法。在里面读取自定义的styleable属性,来控制PictureCarousel的行为,比如轮播的间隔时间。

public class PictureCarousel extends RelativeLayout {

public PictureCarousel(Context context, AttributeSet attrs) {
        super(context, attrs);

        this.mContext = context;
        DisplayMetrics dm = new DisplayMetrics();
        ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);
        WIDTH = dm.widthPixels;
        HEIGHT = dm.heightPixels;
        DENSITY = dm.density;

        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        layoutInflater.inflate(R.layout.widget_picture_carousel, this);

        mViewPager = (ViewPager)findViewById(R.id.viewPager);

        mDotLayout = (LinearLayout)findViewById(R.id.dot_indicator_layout);

        tvDesc = (TextView)findViewById(R.id.tv_pic_carousel_desc);

        TypedArray tArray = context.obtainStyledAttributes(attrs, R.styleable.PictureCarousel);

        initWidget(tArray);
    }

private void initWidget(TypedArray tArray) {
        //float ratio = tArray.getFloat(R.styleable.PictureCarousel_ratio,1.0f);
        //HEIGHT = (int)(WIDTH * ratio);

        //轮播间隔时间,如果不设置默认4秒
        period = tArray.getInt(R.styleable.PictureCarousel_duration,4000);

        tArray.recycle();
    }
}

values/attrs.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PictureCarousel">
        <!--the value of height/width -->
        <attr name="ratio" format="float"/>
        <!--the duration of automatic slide -->
        <attr name="duration" format="integer"/>
    </declare-styleable>
</resources>

布局文件如下:layout/widget_picture_carousel.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

    <TextView android:id="@+id/tv_pic_carousel_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#6888"
        android:textColor="@color/white"
        android:padding="3dp"
        android:textSize="12sp"
        android:layout_alignParentBottom="true"
        android:singleLine="true"/>
    <LinearLayout android:id="@+id/dot_indicator_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_above="@id/tv_pic_carousel_desc"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp">
    </LinearLayout>
</RelativeLayout>

使用ViewPager实现左右无限滑动

PicCarouselActivity定义的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.idengpan.androidwidgte.activity.PicCarouselActivity"
    tools:showIn="@layout/activity_pic_carousel">
    <com.idengpan.androidwidgte.widget.PictureCarousel
        android:id="@+id/widget_picture_carousel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:ratio="0.5"
        app:duration="3000"
        android:background="@color/gray">
    </com.idengpan.androidwidgte.widget.PictureCarousel>
</RelativeLayout>

ViewPager本身不像Gallery支持左右无限滑动,之前查到博客(地址http://blog.csdn.net/gaojinshan/article/details/18038181)里有人使用Views和Data分离,前后增加移位来实现这个功能,很巧妙,但我觉得性能不好,如果图片较多,一次全部加载进内存且还增加两张图片,内存被无端的消耗,最终效果也没有平滑的滚动,很不优雅。后来想起在android-support-v4的新版包(最新的是android-support-v4:23.2.0)中有FragmentStatePagerAdapter这样的适配器,在Android最佳实践的文章中我有讲到它的用处,是在Fragment不定时适配用,刚好我们无限滑动的需求可以用到这个适配器。于是,我的实现如下:

adapter = new CyclePagerAdapter(context.getSupportFragmentManager(),data);
mViewPager.setAdapter(adapter);
mViewPager.addOnPageChangeListener(adapter);
//初始显示第一个描述DESC
tvDesc.setText(data.get(0).desc);
//初始化圆点
for(int i = 0 ;i < data.size();i++){
      ImageView dot = new ImageView(getContext());
      LinearLayout.LayoutParams layoutParams = new             LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
      layoutParams.rightMargin = 10;
      dot.setImageResource(R.drawable.dot_white);
      dot.setLayoutParams(layoutParams);
      mDotLayout.addView(dot);
 }
 //默认第一个选中
 ((ImageView)mDotLayout.getChildAt(0)).setImageResource(R.drawable.dot_red);

public class CyclePagerAdapter extends FragmentStatePagerAdapter implements ViewPager.OnPageChangeListener{
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(int position) {
            tvDesc.setText(data.get(position % data.size()).desc);
            if(mDotLayout != null && mDotLayout.getChildCount() > 0){
                for(int i= 0 ;i < mDotLayout.getChildCount() ;i++){
                    ((ImageView)mDotLayout.getChildAt(i)).setImageResource(R.drawable.dot_white);
                }
                ((ImageView)mDotLayout.getChildAt(position % data.size())).setImageResource(R.drawable.dot_red);
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {
        }

        private DisplayImageOptions options;
        private ImageLoader imageLoader;

        private List<PictureItem> data;

        public CyclePagerAdapter(FragmentManager fm, List<PictureItem> data) {
            super(fm);
            this.data = data;
            options = new DisplayImageOptions.Builder()
                    .resetViewBeforeLoading(true)
                    .cacheOnDisk(true)
                    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
                    .bitmapConfig(Bitmap.Config.RGB_565)
                    .considerExifParams(true)
                    .displayer(new FadeInBitmapDisplayer(300))
                    .build();
            imageLoader =  ImageLoader.getInstance();
            imageLoader.init(ImageLoaderConfiguration.createDefault(getContext()));
        }

        @Override
        public Fragment getItem(int i) {
            SinglePicFragment fragment = new SinglePicFragment();
            Bundle args = new Bundle();
            args.putInt(SinglePicFragment.ARG_POSITION, i % data.size());
            fragment.setArguments(args);
            fragment.options = options;
            fragment.imageLoader = imageLoader;
            fragment.pi = data.get(i % data.size());
            return fragment;
        }

        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }
    }

    public static class SinglePicFragment extends Fragment {

        public static final String ARG_POSITION = "object";
        public PictureItem pi;

        public DisplayImageOptions options;
        public ImageLoader imageLoader;

        public SinglePicFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater,
                                 ViewGroup container, Bundle savedInstanceState) {
            ImageView iv = new ImageView(getContext());
            iv.setScaleType(ImageView.ScaleType.FIT_XY);
            imageLoader.displayImage(pi.imageUrl, iv, options);
            iv.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getContext().getApplicationContext(),getArguments().getInt(ARG_POSITION)+": TargetUrl",Toast.LENGTH_SHORT).show();
                }
            });
           Log.d(TAG,(Integer.toString(getArguments().getInt(ARG_POSITION)+1) + "Fragment Created!! 分配内存"));
            return iv;
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            Log.d(TAG, (Integer.toString(getArguments().getInt(ARG_POSITION) + 1) + "Fragment Destroyed!! 释放内存"));
        }
    }

    public static class PictureItem{
        public String imageUrl;
        public String targetUrl;
        public String desc;
    }

首先定义了PictureItem的Bean。定义了SinglePicFragment作为消费片段,不断的生成和销毁。核心就是自定义的适配器CyclePagerAdapter,同时也让这个适配器实现了PageChanged的监听,getCount()方法返回Integer.MAX_VALUE。这样,就可以无限循环了。使用CyclePagerAdapter,内存的利用就比较合理了,不管滑动多少个,内存中都只有图片内存的占用。内存波动如下:
mem_wave

Timer + Handler实现定时自动滑动

private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0x1){
                //定时器实现无限循环
                int index = (mViewPager.getCurrentItem()+1) % adapter.getCount();
                mViewPager.setCurrentItem(index,true);
            }
        }
    };


 timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    mHandler.sendEmptyMessage(0x1);
                }
}, period, period);

前面通过period = tArray.getInt(R.styleable.PictureCarousel_duration,4000);读取styleable的属性period来设置滑动的间隔时间,如果没有配置的话,就默认4秒间隔。使用Handler发送消息实现滑动。

图片描述和圆点焦点切换的实现

在onPageSelected方法中实现切换:

@Override
        public void onPageSelected(int position) {
            tvDesc.setText(data.get(position % data.size()).desc);
            if(mDotLayout != null && mDotLayout.getChildCount() > 0){
                for(int i= 0 ;i < mDotLayout.getChildCount() ;i++){
                    ((ImageView)mDotLayout.getChildAt(i)).setImageResource(R.drawable.dot_white);
                }
                ((ImageView)mDotLayout.getChildAt(position % data.size())).setImageResource(R.drawable.dot_red);
            }
        }

圆点图片,我没有使用png图片,直接使用Android自带的shape图片即可:颜色可以自定义,大小可以自定义,不用像png图片解析,这是比图片更方便更有优势的地方。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval" android:useLevel="false">
    <solid android:color="@color/white"/>
    <size android:width="5dp"
        android:height="5dp"/>
</shape>

最后,在Activity中设置轮播图片显示的长宽比例。和服务器的图片比例一致,这样显示的比较匀称。

DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
mPictureCarousel.setLayoutParams(new RelativeLayout.LayoutParams( dm.widthPixels, (int)(dm.widthPixels * 0.5)));

优化思路讨论

1、上面的CyclePagerAdapter中,不停的生产和消费Fragment,实现了内存的平衡,但系统对内存很敏感,不停的内存分配和回收,会不会使Android的GC不停的工作,影响性能?有无既能保证只有3个内存,又不重复分配释放内存的方法?
2、上面的圆点我使用ImageView,然后切换时每次都全部设置背景图片来切换,系统绘制的会比较频繁,这个地方可以优化。我在开发过程中尝试过自定义View,然后在onDraw中绘制圆点,通过计算每个圆点的坐标和设置半径在Layout中添加,但一直只能看到一个不知原因。如果有人可以实现,估计性能会更好。
3、定时器Timer,Android官方建议使用新的类ScheduledThreadPoolExecutor 。地址http://developer.android.com/reference/java/util/Timer.html
4、更多优化建议…Github地址:https://github.com/bendeng/PictureCarousel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值