ViewPager 实现 Galler 效果, 中间大图显示,两边小图展示

时间:2022-04-26
本文章向大家介绍ViewPager 实现 Galler 效果, 中间大图显示,两边小图展示,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

正常情况下, ViewPager 一页只能显示一项数据, 但是我们常常看到网上,特别是电视机顶盒的首页经常出现中间大图显示两端也都露出一点来,这种效果怎么实现呢?先上一张效果图:

大家第一眼肯定想到了Gallery,这是最早android图库自带的效果,现在基本不用,那有没有其他好的办法呢?我们首先考虑的还是ViewPager+PagerAdapter的实现策略。

后面在网上了搜了一下, 发现要实现上面的效果,我们需要注意两个方面,首先是怎么在两边显示两个小图,第二,怎么实现无限滑动。

1,首先就是用到了View的android:clipChildren属性,.简单来说父View是默认是束缚子View 的显示范围的,所以当我们在父View有 padding , 那么 子View 则在 padding区域是不能显示内容的。当设置android:clipChildren="false"的时候,子View 就可以在父View 的padding内容区域显示内容了。

2,实现无限循环很简单,网上也有很多的解决方案,我这里不考虑性能上的东西,且看下面简单的代码:

private class ImageAdapter extends PagerAdapter{  
           
        private ArrayList<String> viewlist;  
   
        public ImageAdapter(ArrayList<String> viewlist) {  
            this.viewlist = viewlist;  
        }  
   
        @Override  
        public int getCount() {  
            //设置成最大,使用户看不到边界,大家可以去查询下这个大小  
            return Integer.MAX_VALUE;  
        }       
         @Override    
         public void destroyItem(ViewGroup container, int position,    
                 Object object) {    
             //注:不要在这里调用removeView  
         }   
		 
         @Override    
         public Object instantiateItem(ViewGroup container, int position) {  
             //对ViewPager页号求模取出View列表中要显示的项  
             position %= viewlist.size();  
             if (position<0){  
                 position = viewlist.size()+position;  
             }  
			 
			 //这里是view
            ViewHolder viewHolder = null;
            View view = LayoutInflater.from(mContext).inflate(
                R.layout.item_finefare_layout, null);
          if (viewHolder == null) {
            viewHolder = new ViewHolder(view);
           }
            bindView(viewHolder, data);

          container.addView(view, LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
             return view;    
         }    
    }  

上面代码应该注意的几点:

  • getCount() 方法的返回值:这个值直接关系到ViewPager的“边界”,因此当我们把它设置为Integer.MAX_VALUE之后,用户基本就看不到这个边界了(估计滑到这里的时候电池已经挂了吧o_O)。当然,通常情况下设置为100倍实际内容个数也是可以的,之前看的某个实现就是这么干的。
  • instantiateItem() 方法position的处理:由于我们设置了count为 Integer.MAX_VALUE,因此这个position的取值范围很大很大,但我们实际要显示的内容肯定没这么多(往往只有几项),所以这里肯定会有求模操作。但是,简单的求模会出现问题:考虑用户向左滑的情形,则position可能会出现负值。所以我们需要对负值再处理一次,使其落在正确的区间内。
  • instantiateItem() 方法父组件的处理:通常我们会直接addView,但这里如果直接这样写,则会抛出IllegalStateException。假设一共有三个view,则当用户滑到第四个的时候就会触发这个异常,原因是我们试图把一个有父组件的View添加到另一个组件。

经过上面的解释,我们已经很清楚了,以下是代码的详细实现,数据来源于网上,大家可以自行模拟:

ViewPager类:

public class WelfareAdapter extends PagerAdapter {

    private Context mContext;
    private List<PanicBean> dataList = new ArrayList<>();

    public WelfareAdapter(Context mContext) {
        this.mContext = mContext;
    }

    public void setDatas(List<PanicBean> list) {
        if (list.size() <= 0) {
            dataList.clear();
            notifyDataSetChanged();
            return;
        }
        dataList.clear();
        dataList.addAll(list);
        notifyDataSetChanged();
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        position %= dataList.size();
        if (position<0){
            position = dataList.size()+position;
        }

        PanicBean data = dataList.get(position);

        ViewHolder viewHolder = null;
        View view = LayoutInflater.from(mContext).inflate(
                R.layout.item_finefare_layout, null);
        if (viewHolder == null) {
            viewHolder = new ViewHolder(view);
        }
        bindView(viewHolder, data);

        container.addView(view, LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
        return view;
    }

    private void bindView(ViewHolder viewholder, final PanicBean data) {
        Glide.with(mContext).load(data.pic).into(viewholder.welfareImage);

        viewholder.welfareImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ToastUtils.showToast("你点击了"+data.href);
            }
        });
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
//        container.removeView((View) object);
    }

    class ViewHolder {
        @BindView(R.id.welfare_image)
        RoundedImageView welfareImage;

        ViewHolder(View view) {
            ButterKnife.bind(this, view);
            view.setTag(this);
        }

        public void reset() {
            welfareImage.setBackground(mContext.getResources().getDrawable(R.drawable.welfare_default_icon));
        }
    }

}

用到的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ptr="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <com.yju.app.widght.image.RoundedImageView
        android:id="@+id/welfare_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:scaleType="fitXY"
        ptr:corner_radius="3dp"
        android:src="@drawable/welfare_default_icon" />

</LinearLayout>

用到的数据Bean(这个大家根据情况自行模拟,只要是个列表就行)

public class FineFareEntity {

    public List<PanicBean> panic;

    public static class PanicBean {
        public String id;
        public long endtime;
        public String pic;
        public int type;
        public String href;
        public String title;
        public String share;
    }
}

为了方便使用我们都自定义成View,方便以后代码维护:

public class WelfareView extends SimpleLinearLayout {

    @BindView(R.id.finefare_count)
    TextView finefareCount;
    @BindView(R.id.viewPager)
    ViewPager viewPager;
    @BindView(R.id.finefare_name)
    TextView finefareName;
    @BindView(R.id.welfare_view)
    LinearLayout welfareView;

    private WelfareAdapter adapter = null;
    private List<PanicBean> welfareList = null;

    public WelfareView(Context context) {
        super(context);
    }

    public WelfareView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void initViews() {
        contentView = inflate(mContext, R.layout.layout_welfare, this);
        ButterKnife.bind(this);

        initViewPager();

        initTouch();

    }

    private void initTouch() {
        //这里要把父类的touch事件传给子类,不然边上的会滑不动
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return viewPager.dispatchTouchEvent(event);
            }
        });

        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                position %= welfareList.size();
                if (position<0){
                    position = welfareList.size()+position;
                }
                finefareName.setText(welfareList.get(position).id);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

    }

    private void initViewPager() {
        viewPager.setOffscreenPageLimit(3);
        viewPager.setPageTransformer(true, new ScalePagerTransformer());
        //设置Pager之间的间距
        viewPager.setPageMargin(UIUtils.dp2px(mContext, 15));

        adapter = new WelfareAdapter(mContext);
        viewPager.setAdapter(adapter);
    }

    public void setWelfareData(List<PanicBean> datas) {
        this.welfareList = datas;
        welfareView.setVisibility(datas.size()>0?VISIBLE:GONE);
        finefareCount.setText("共有" + datas.size() + "个福利");
        finefareName.setText(welfareList.get(getCurrentDisplayItem()).title);

        adapter = new WelfareAdapter(mContext);
        adapter.setDatas(datas);
        viewPager.setAdapter(adapter);
        viewPager.setCurrentItem(adapter.getCount() > 0 ? 1 : 0, true);

    }

    public int getCurrentDisplayItem() {
        if (viewPager != null) {
            return viewPager.getCurrentItem();
        }
        return 0;
    }

}

这里有一个滑动缩放的类:

public class ScalePagerTransformer implements ViewPager.PageTransformer {

        private static final float MIN_SCALE = 0.85f;
        private static final float MIN_ALPHA = 0.5f;

        @Override
        public void transformPage(View view, float position) {
            if (position >= -1 || position <= 1) {
                final float height = view.getHeight();
                final float width = view.getWidth();
                final float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
                final float vertMargin = height * (1 - scaleFactor) / 2;
                final float horzMargin = width * (1 - scaleFactor) / 2;

                view.setPivotY(0.5f * height);
                view.setPivotX(0.5f * width);

                if (position < 0) {
                    view.setTranslationX(horzMargin - vertMargin / 2);
                } else {
                    view.setTranslationX(-horzMargin + vertMargin / 2);
                }

                view.setScaleX(scaleFactor);
                view.setScaleY(scaleFactor);

                view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
            }
        }
    }
<pre name="code" class="html" style="color: rgb(51, 51, 51); font-size: 14px; line-height: 26px;">SimpleLinearLayout 类
public class SimpleLinearLayout extends LinearLayout {

    protected Context mContext;
    protected View contentView;
    protected AtomicBoolean isPreparingData;

    public SimpleLinearLayout(Context context) {
        super(context);
        this.mContext = context;
        isPreparingData = new AtomicBoolean(false);
        initViews();
    }

    public SimpleLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        isPreparingData = new AtomicBoolean(false);
        initViews();
    }

    protected void initViews() {

    }

}

用到的布局(android:clipChildren="false"需要注意):

<span style="color:#333333;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/welfare_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    </span><span style="color:#ff0000;">android:clipChildren="false"</span><span style="color:#333333;">
    android:layout_marginTop="10dp"
    android:background="@color/c12"
    android:orientation="vertical">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <View
            style="@style/vertical_bold_line_c8"
            />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="10dp">

            <TextView
                style="@style/style_c6_s16"
                android:layout_centerVertical="true"
                android:text="精品福利" />

            <TextView
                android:id="@+id/finefare_count"
                style="@style/style_c6_s14"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:drawablePadding="3dp"
                android:drawableRight="@drawable/arrow"
                android:text="共有n个福利" />
        </RelativeLayout>
    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="140dp"
        android:layout_marginLeft="45dp"
        android:layout_marginRight="45dp"
         />

    <TextView
        android:id="@+id/finefare_name"
        style="@style/style_c8_s16"
        android:layout_gravity="center"
        android:layout_marginBottom="15dp"
        android:layout_marginTop="15dp"
        android:text="" />
</LinearLayout>
</span>

最后就是在我们的主代码中写个歌测试了,

 private void initWelfare() {
        String welfare = FileUtils.readAssert(getActivity(), "welfare.txt");
        FineFareEntity entity=JsonUtils.parseJson(welfare,FineFareEntity.class);
        if (entity!=null){
            welfareView.setWelfareData(entity.panic);
        }
    }

涉及到的布局:

<com.yju.app.shihui.welfare.view.WelfareView
                android:id="@+id/welfare_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="160dp"
                />

这里的FileUtils.readAssert 代码:

public static String readAssert(Context context, String fileName){
        String resultString="";
        try {
            InputStream inputStream=context.getResources().getAssets().open(fileName);
            byte[] buffer=new byte[inputStream.available()];
            inputStream.read(buffer);
            resultString=new String(buffer,"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultString;
    }

最后为了方便大家的模拟,我把数据给大家,有点多,大家自己放到assert目录下,

{
panic: [
{
id: "2412",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M04/E3/67/CgvUBVe9vyOAV47CAACXZZs5GrU558_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n770",
title: "今日限时抢",
share: ""
},
{
id: "2417",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M09/E4/37/CgvUA1e91VmAMYrwAAC5qcblOUg650_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n781",
title: "今日限时抢",
share: ""
},
{
id: "2413",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M05/E3/D4/CgvUA1e9v2SAVf3qAAB9GcBIWYA268_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n771",
title: "今日限时抢",
share: ""
},
{
id: "2414",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M05/E3/69/CgvUBVe9v4aAMaFRAABWy73vn2g252_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n772",
title: "今日限时抢",
share: ""
},
{
id: "2415",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M06/E3/02/CgvUBFe9v6WAP85NAAC6EK5e5Vg469_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n773",
title: "今日限时抢",
share: ""
},
{
id: "2416",
endtime: 1472097600000,
pic: "http://pc1.img.ymatou.com/G02/M06/E3/02/CgvUBFe9v8CAHyXVAACELcKFT_M328_o.jpg",
type: 1,
href: "http://evt.ymatou.com/n775",
title: "今日限时抢",
share: ""
}
]
}

其实针对上面的代码,有个bug,就是上面的代码虽然实现了滑动,却没有真正的实现左右滑动,针对上面的问题,请看下一篇优化篇。

优化方案