美文网首页优秀案例AndroidAndroid
Android轮播图效果的各种实现

Android轮播图效果的各种实现

作者: 砺雪凝霜 | 来源:发表于2017-05-14 17:57 被阅读6532次

    前言

    很多APP的首页通常会有一个带有动画切换的各种轮播图效果,刚好新项目中也要实现轮播图的效果,于是便研究了Android平台下各种轮播效果,网上也有很多实现轮播相关的方案,但是质量参差不齐,为此踩了不少的坑。下面就来关于轮播图实现方面的一些学习心得,希望对大家有所帮助。

    **(一)使用ViewPager实现轮播图切换效果 **

    开源项目:Android-Coverflow
    GitHub地址:https://github.com/crosswall/Android-Coverflow
    其效果如下:

    Gif_20170430_114547.gif

    **(1)ViewPager切换动画实现原理 **

    • 使用View.PageTransformer实现动画切换效果,ViewPager的动画切换效果都是重写PageTransformer这个类来实现的。PageTransformer方法中就一个transformPage方法,里面有两个参数,viewPager的滑动时的子View,这个很好理解,最难理解的是这个position参数。切换时的动画实现主要靠position来显示。下面来介绍其中含义。
     public interface PageTransformer {
            void transformPage(View page, float position);
        }
    

    ViewPager左右切换时,position的值范围说明:

    • **(-oo,-1) 相对于左边第一页,其左边的所有页面 **
    • *** [-1, 0 ) 相对于当前选中页,其左边的第一页**
    • *** [0, 1 ) 相对于当前选中页,其右边第一页 **
    • [1,+oo) 相对于右边第一页,其右边的所有页面

    下面举例说明:

    • **当前ViewPage选中的页为,其右边的页面为B,现在向左滑动A,慢慢由页面A切换到页面B **

    **页面A的position值是由 0 慢慢减小到 -1 [0,-1] **
    **页面B的position值是由 1 慢慢减小到 0 [1,0] **

    **此时页面B为ViewPage当前选中的页面 **

    • **再向右滑动页面B,慢慢由页面B切换到页面A **

    **页面A的position值由 -1 慢慢增加到 0 [-1,0] **
    页面B的position值由 0 慢慢增加到 1 [ 0,1]

    理解了transformPage方法中position的含义,那么ViewPager动画切换效果的实现就很好理解了。下面是项目中我实现的一个ViewPager切换效果,如果所示:

    代码:

    public class ZoomPageTransformer implements ViewPager.PageTransformer {
        private static final float MAX_SCALE = 1.0f;
    
        private static final float MIN_SCALE = 0.85f;//0.85f
    
        private static final  float MIN_ALPHA = 0.3f;
    
        private static final String TAG = "PageTransformer";
        @Override
        public void transformPage(View view, float position) {
            //setScaleY只支持api11以上
            if (position < -1) {
                view.setScaleX(MIN_SCALE);
                view.setScaleY(MIN_SCALE);
                view.setAlpha(MIN_ALPHA);//左边的左边的Page
            } else if (position <= 1) {
                float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
                if (position > 0) {
                    view.setTranslationX(-scaleFactor);
                } else if (position < 0) {
                    view.setTranslationX(scaleFactor);
                }
                view.setScaleY(scaleFactor);
                view.setScaleX(scaleFactor);
    
               // float alpha = 1f -  Math.abs(position) * (1 - );
    
                float alpha = MIN_ALPHA + (1 - MIN_ALPHA) * (1 - Math.abs(position));
                view.setAlpha(alpha);
    
                Log.i(TAG,"position = " + position + " alpha = " + alpha);
    
            } else { // (1,+Infinity]
    
                view.setScaleX(MIN_SCALE);
                view.setScaleY(MIN_SCALE);
                view.setAlpha(MIN_ALPHA);
            }
        }
    }
    
    

    当position的范围在(-oo,-1) 和[1,+oo)时,view的比例缩小MIN_SCALE(0.85f),透明度缩小到 MIN_ALPHA(0.3f)

    当position的范围在 [-1,1]的时候,View的scale值和position绝对值成反比

     float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
                if (position > 0) {
                    view.setTranslationX(-scaleFactor);
                } else if (position < 0) {
                    view.setTranslationX(scaleFactor);
                }
                view.setScaleY(scaleFactor);
                view.setScaleX(scaleFactor);
    
    
    • 当position的值等于-1或者1的时候,此时View就是左边第一页和右边第一页,此时scale值就是MIN_SCALE(0.85f)

    • 当position的值范围在[-1,0]时,scale的值慢慢由MIN_SCALE变大到MAX_SCALE,也就是说ViewPager右滑动时,左边第一个View是慢慢放大的,直到其放大到MAX_SCALE。

    • 当position的值范围在(0,1]时,scale的值由MAX_SCALE变小到MIN_SCALE,也就是说ViewPager右滑动时,右边第一个View是慢慢缩小的,直到其比例缩放到MIN_SCALE。

    透明度变化也是同样一个原理。

    **(2)让ViewPager显示多页 **

    一般情况下,ViewPager只能显示一页,那如何让其显示多个子页面呢?那就不得不说setClipChildren(false)这个方法了。

    • 默认情况下为setClipChildren(true),如果子View的布局范围超过了父View,那么它的边界将会被裁减掉,也就是说超过父View的部分是看不到的。
    • 当setClipChildren(false)的情况下,子View的布局范围超过了父View部分将不会被裁减掉 ,而将会以动画的形式显示出来。
         <me.crosswall.lib.coverflow.core.PagerContainer
            android:id="@+id/pager_container"
            android:layout_width="match_parent"
            android:layout_height="220dp"
            android:clipChildren="false"
            android:background="?attr/colorPrimary">
    
            <android.support.v4.view.ViewPager
              android:id="@+id/overlap_pager"
                android:layout_width="300dp"
                android:layout_height="200dp"
                android:layout_gravity="center" />
    
        </me.crosswall.lib.coverflow.core.PagerContainer>
    

    Layout.xml

    <com.mtime.cinema.business.recommend.widget.BannerView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="@dimen/recommend_banner_height"
        android:clipChildren="false">
    
        <android.support.v4.view.ViewPager
            android:id="@+id/fragment_recommend_viewPager"
            android:layout_width="@dimen/recommend_banner_image_width"
            android:layout_height="@dimen/recommend_banner_image_height"
            android:layout_centerHorizontal="true"
            android:clipChildren="false" />
    
        <LinearLayout
            android:id="@+id/fragment_recommend_banner_indicator"
            android:layout_alignParentBottom="true"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:gravity="center_horizontal"
            android:orientation="horizontal" />
    
    
    </com.mtime.cinema.business.recommend.widget.BannerView>
    

    JAVA代码

    /**
     * Created by liuyu on 2017/4/17.
     * 推荐页轮播图控件
     */
    
    public class BannerView extends RelativeLayout {
    
        private static final String TAG = "BannerView";
    
        private LinearLayout mBannerIndicator;
    
        private ViewPager mViewPager;
    
        private long VIEWPAGER_SWITCH_DURING = 8000;//轮播时间
    
        private int MSG_START_SCROLL = 100;//消息的名称
    
        private int mPointRadius;
    
        private int mPointTotalCount;//小圆点真正个数,有可能接口返回数据记录 < DEFAULT_POINT_COUNT
    
        private Drawable mNormalColor, mSelectedColor;
    
        private BannerAdapter mBannerAdapter;
        private int mPointMarginTop;
    
        public BannerView(Context context) {
            super(context);
            initBanner();
        }
    
        public BannerView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initBanner();
        }
    
        private void initBanner() {
            this.setClipChildren(false);
            mPointRadius = getContext().getResources().getDimensionPixelSize(R.dimen.recommend_banner_point_radius);
            mPointMarginTop = getContext().getResources().getDimensionPixelSize(R.dimen.recommend_banner_point_margin_top);
    
            mNormalColor = getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_normal);
            mSelectedColor = getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_selected);
        }
    
        public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initBanner();
        }
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_START_SCROLL) {
                    if (mHandler != null) {
                        mHandler.removeMessages(MSG_START_SCROLL);
                        mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
                        mHandler.sendEmptyMessageDelayed(MSG_START_SCROLL, VIEWPAGER_SWITCH_DURING);
                    }
                }
            }
        };
    
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            mBannerIndicator = (LinearLayout) findViewById(R.id.fragment_recommend_banner_indicator);
            mViewPager = (ViewPager) findViewById(R.id.fragment_recommend_viewPager);
        }
    
        /**
         * 添加小圆点
         */
        private void addDots(int count) {
            mPointTotalCount = count;
            // mBannerIndicator.removeAllViews();
            Logger.i(TAG, "addDots count = " + count);
            //加点
            for (int i = 0; i < count; i++) {
                ImageView pointView = new ImageView(getContext());
    
                pointView.setImageDrawable(getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_normal));
                //点的大小
                LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mPointRadius * 2, mPointRadius * 2);
                //点的间隔
                layoutParams.leftMargin = mPointRadius * 2;
                //居中显示
                layoutParams.topMargin = mPointMarginTop;
    
                Logger.i(TAG, "addDots index = " + i);
    
                pointView.setLayoutParams(layoutParams);
                //把点添加到容器中
                mBannerIndicator.addView(pointView);
            }
    
        }
    
        /**
         * @param dataList
         */
        public void show(List<BannerBean> dataList) {
    
            Logger.i(TAG, "start show banner");
    
            if (dataList == null || dataList.size() == 0) {
                return;
            }
    
          /*  if (mViewPager.getChildCount() > 0 && mBannerIndicator.getChildCount() > 0) {
                mBannerAdapter.notifyDataSetChanged();
            }*/
    
    
            //重置数据
    
            if (mViewPager.getChildCount() > 0) {
                mViewPager.removeAllViews();
            }
    
            if (mBannerIndicator.getChildCount() > 0) {
                mBannerIndicator.removeAllViews();
            }
    
    
            addDots(dataList.size());
    
            /**** 重要部分  ******/
            //clipChild用来定义他的子控件是否要在他应有的边界内进行绘制。 默认情况下,clipChild被设置为true。 也就是不允许进行扩展绘制。
            mViewPager.setClipChildren(false);
            //父容器一定要设置这个,否则看不出效果
    
    
            //mViewPager.setPageMargin(getResources().getDimensionPixelOffset(R.dimen.recommend_banner_image_space));
    
            mViewPager.setOffscreenPageLimit(5);
    
    
            final List<String> imageList = new ArrayList<>();
          //add image url
    
            mBannerAdapter = new BannerAdapter(getContext(), dataList);
            mBannerAdapter.setImageList(imageList);
    
            mViewPager.setAdapter(mBannerAdapter);
    
            int currentItem = Integer.MAX_VALUE / 2;
    
            mViewPager.setCurrentItem(currentItem);
    
            changeIndicatorStatus(currentItem);
    
            //设置ViewPager切换效果,即实现画廊效果
            mViewPager.setPageTransformer(true, new ZoomPageTransformer());
    
    
            //将容器的触摸事件反馈给ViewPager
            this.setOnTouchListener(new OnTouchListener() {
    
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    // dispatch the events to the ViewPager, to solve the problem that we can swipe only the middle view.
                    return mViewPager.dispatchTouchEvent(event);
                }
    
            });
            //图片数量大于1的时候,才进行自动轮播
            if (dataList.size() > 1) {
                startAutoScroll();
            }
    
            mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    
                }
    
                @Override
                public void onPageSelected(int position) {
                    changeIndicatorStatus(position);
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
    
                }
            });
    
        }
    
    
        /**
         * 开始自动切换
         */
        public void startAutoScroll() {
            if (mHandler != null) {
                mHandler.removeMessages(MSG_START_SCROLL);
                mHandler.sendEmptyMessageDelayed(MSG_START_SCROLL, VIEWPAGER_SWITCH_DURING);
            }
        }
    
        /**
         * 停止自动切换
         */
        public void stopScroll() {
            mHandler.removeMessages(MSG_START_SCROLL);
        }
    
        public void changeIndicatorStatus(int position) {
            if (position == 0) {
                return;
            }
    
            int realPos = position % mPointTotalCount;
    
            for (int i = 0; i < mPointTotalCount; i++) {
                if (i == realPos) {
                    ((ImageView) mBannerIndicator.getChildAt(i)).setImageDrawable(mSelectedColor);
                } else {
                    ((ImageView) mBannerIndicator.getChildAt(i)).setImageDrawable(mNormalColor);
                }
            }
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            clear();
        }
    
        /**
         * 数据清理
         */
        public void clear() {
            if (mHandler != null) {
                mHandler.removeCallbacksAndMessages(null);
            }
        }
    }
    

    (二)使用FancyCoverFlow实现轮播图切换效果

    Github地址:https://github.com/davidschreiber/FancyCoverFlow

    (1) FancyCoverFlow的使用

    • Java代码
        this.fancyCoverFlow.setUnselectedAlpha(0.0f);
    
             // 未选中的饱和度
             this.fancyCoverFlow.setUnselectedSaturation(0.0f);
    
             // 未选中的比例
             this.fancyCoverFlow.setUnselectedScale(0.8f);
    
             // child间距
             this.fancyCoverFlow.setSpacing(-60);
    
             // 旋转度数
             this.fancyCoverFlow.setMaxRotation(0);
    
             // 非选中的重心偏移,负的向上
             this.fancyCoverFlow.setScaleDownGravity(-1f);
    
             // 作用距离
             this.fancyCoverFlow.setActionDistance(FancyCoverFlow.ACTION_DISTANCE_AUTO);
    
    • XML布局文件
    <at.technikum.mti.fancycoverflow.FancyCoverFlow
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            fcf:maxRotation="45"
            fcf:unselectedAlpha="0.3"
            fcf:unselectedSaturation="0.0"
            fcf:unselectedScale="0.4" />
    

    (2) 实现无限循环效果

    FancyCoverFlow是通过继承Galley实现的,那么我们可以利用Galley的setSelection()方法实现一些特殊的效果,例如打造无限循环的录播图。

    FancyCoverFlowSampleAdapter.java

    public class FancyCoverFlowSampleAdapter extends FancyCoverFlowAdapter {
    
        // =============================================================================
        // Private members
        // =============================================================================
    
        private int[] images = {
                R.drawable.image1,  R.drawable.image2,  R.drawable.image3,
                R.drawable.image6, R.drawable.image5,  R.drawable.image4
    
               };
    
        // =============================================================================
        // Supertype overrides
        // =============================================================================
    
        @Override
        public Integer getItem(int i) {
            return images[ i];
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }
    
        @Override
        public View getCoverFlowItem(int i, View reuseableView, ViewGroup viewGroup) {
            ImageView imageView = null;
    
            if (reuseableView != null) {
                imageView = (ImageView) reuseableView;
            } else {
                imageView = new ImageView(viewGroup.getContext());
                imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
                imageView.setLayoutParams(new FancyCoverFlow.LayoutParams(300, 400));
    
            }
    
            imageView.setImageResource(this.getItem(i % images.length));
            return imageView;
        }
    
    
    }
    

    MainActivity.java

    
            this.fancyCoverFlow = (FancyCoverFlow) this.findViewById(R.id.fancyCoverFlow);
    
            this.fancyCoverFlow.setAdapter(new FancyCoverFlowSampleAdapter());
            this.fancyCoverFlow.setUnselectedAlpha(1.0f);
            this.fancyCoverFlow.setUnselectedSaturation(0.0f);
            this.fancyCoverFlow.setUnselectedScale(0.6f);
            this.fancyCoverFlow.setSpacing(-20);
            this.fancyCoverFlow.setMaxRotation(40);
            this.fancyCoverFlow.setScaleDownGravity(0.2f);
            this.fancyCoverFlow.setActionDistance(FancyCoverFlow.ACTION_DISTANCE_AUTO);
            this.fancyCoverFlow.setSelection(Integer.MAX_VALUE/2);
    

    效果如下:

    Gif_20170514_171512.gif

    (三)使用RecyclerCoverFlow实现轮折叠的轮播图效果

    Github地址:https://github.com/ChenLittlePing/RecyclerCoverFlow

    效果如下:

    Gif_20170514_172656.gif

    ** (1) RecyclerCoverFlow的使用**

    • xml布局
        <recycler.coverflow.RecyclerCoverFlow
                android:id="@+id/list"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
        </recycler.coverflow.RecyclerCoverFlow>
    
    • Activity中引入
     mList = (RecyclerCoverFlow) findViewById(R.id.list);
        //        mList.setFlatFlow(true); //平面滚动
        mList.setAdapter(new Adapter(this));
        mList.setOnItemSelectedListener(new CoverFlowLayoutManger.OnSelected() {
            @Override
            public void onItemSelected(int position) {
                ((TextView)findViewById(R.id.index)).setText((position+1)+"/"+mList.getLayoutManager().getItemCount());
            }
        });
    

    (2) 实现原理

    RecyclerCoverFlow是通过集成RecyclerView实现的,由于默认情况下,ViewGroup中的子view绘制顺序,index越大,其绘制顺序会越靠后,所以后面的子View会遮住前面的子view,导致居中显示的子View右边重叠部分会被靠后的子View遮住。

    
        @Override
        protected int getChildDrawingOrder(int childCount, int i) {
            int center = getCoverFlowLayout().getCenterPosition()
                    - getCoverFlowLayout().getFirstVisiblePosition(); //计算正在显示的所有Item的中间位置
            if (center < 0) center = 0;
            else if (center > childCount) center = childCount;
            int order;
            if (i == center) {
                order = childCount - 1;
            } else if (i > center) {
                order = center + childCount - 1 - i;
            } else {
                order = i;
            }
            return order;
        }
    

    相关文章

      网友评论

      本文标题:Android轮播图效果的各种实现

      本文链接:https://www.haomeiwen.com/subject/jtattxtx.html