定义了PictureCarousel类,直接在项目中使用,很方便集成轮播组件。
Github地址:https://github.com/bendeng/PictureCarousel
想起两年前使用Gallery来做轮播组件,当时时不时还要OOM一下,没有使用UIL时,不知内存管理的重要性,没熟练使用ViewPager时,也不知ViewPager的机制对内存的占用是多么的友好。效果图如下:
1、定义PictureCarousel类,继承RelativeLayout
2、使用ViewPager实现左右滑动
3、使用Timer实现定时滑动
4、滑动时切换图片描述和圆点焦点
5、后续优化
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文件如下:
<resources>
<declare-styleable name="PictureCarousel">
<attr name="ratio" format="float"/>
<attr name="duration" format="integer"/>
declare-styleable>
resources>
布局文件如下:layout/widget_picture_carousel.xml
<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>
PicCarouselActivity定义的布局文件如下:
<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 data;
public CyclePagerAdapter(FragmentManager fm, List 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,内存的利用就比较合理了,不管滑动多少个,内存中都只有图片内存的占用。内存波动如下:
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图片解析,这是比图片更方便更有优势的地方。
<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