Android轮播图效果的各种实现
来源:互联网 发布:景区历年游客数据统计 编辑:程序博客网 时间:2024/06/04 19:57
前言
很多APP的首页通常会有一个带有动画切换的各种轮播图效果,刚好新项目中也要实现轮播图的效果,于是便研究了Android平台下各种轮播效果,网上也有很多实现轮播相关的方案,但是质量参差不齐,为此踩了不少的坑。下面就来关于轮播图实现方面的一些学习心得,希望对大家有所帮助。
(一)使用ViewPager实现轮播图切换效果
开源项目:Android-Coverflow
GitHub地址:https://github.com/crosswall/Android-Coverflow
其效果如下:
(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);
(1) 当position的值等于-1或者1的时候,此时View就是左边第一页和右边第一页,此时scale值就是MIN_SCALE(0.85f)
(2) 当position的值范围在[-1,0]时,scale的值慢慢由MIN_SCALE变大到MAX_SCALE,也就是说ViewPager右滑动时,左边第一个View是慢慢放大的,直到其放大到MAX_SCALE。
(3)当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的使用
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);
效果如下:
(三)使用RecyclerCoverFlow实现轮折叠的轮播图效果
Github地址:https://github.com/ChenLittlePing/RecyclerCoverFlow
效果如下:
* (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轮播图效果的各种实现
- Android 各种实现Tab效果的实现方式
- android 自定义菜单 使用PopupWindow实现菜单的各种效果
- Android高级UI ProgressBar实现各种效果的圆形进度
- Android的各种滑动效果
- Android各种loading的效果
- Android相对布局实现各种梅花效果
- Android相对布局实现各种梅花效果
- Android相对布局实现各种梅花效果
- Android相对布局实现各种梅花效果
- android wheel实现各种选择效果
- android ListView各种效果实现总结
- android drawable实践 xml 实现各种效果
- android学习笔记---59_各种图形的使用介绍,android炫酷效果的实现
- 【Android界面实现】listview中item的各种进入效果实现
- 【Android界面实现】listview中item的各种进入效果实现
- Js|---CSS滤镜实现的各种效果
- CButtonST 类:实现各种按钮的效果
- 1072. Gas Station (30)
- 2016.4.30
- poj 1018 dp
- 剑指offer-面试题58-二叉树的下一个结点
- Spark源码学习笔记4-SparkEnv
- Android轮播图效果的各种实现
- CURL
- HDU
- Java工具类——文件操作
- MVP架构在Android平台上的实现分析(一)
- 快速排序及其改进
- JMeter学习笔记2-图形界面简单介绍
- 关于分酒的问题
- window.location.replace和window.location.href区别