作者: 夏至,欢迎转载,但请保留这段申明,谢谢
http://blog.csdn.net/u011418943/article/details/77370490
需求,在首页开发中,我们经常会使用轮播图片的方法,来达到广告栏的效果,而它的实现方式呢,而比较简单,就是一个 viewpager ,然后让它循环起来就可以了。
而它的编写是比较繁琐的,因为除了图片和文字说明外,还需要底部的圆点指示器(你可以写成其他相撞),所以,封装起来,还是可以省很多事情的。
当然,github 有一大堆 轮子可以使用,但为何不自己也搞一个呢?
首先看一下今天要实现的效果:
首先,先看一下普通方式应该怎么实现。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:background="#22e12222"
tools:context="com.rachel.activitytest.MainActivity">
<android.support.v4.view.ViewPager
android:layout_marginTop="20dp"
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
android:clipToPadding="false"
/>
</LinearLayout>
这里就不讲 viewpager 的配置什么的了,因为讲了估计你也不想看,这里讲讲轮播的实现把。
1、首尾填充实现循环
什么意思呢 ? 就是假如我们要轮播的是 三张图片,那么实际上,填充的是 五张 图片,如下面这张图:
即在初始化的时候,1 的前面,填充3,3,的后面填充1,然后再用 viewpager.setCurrentItem(),不动声色的直接设置过去,达到一个移花接木的效果,这样看起来就实现了轮播的效果了。
所以,数据的初始化,如下面所示:
private List<View> initImg() {
for (int i = 0; i < images.length + 2; i++) {
View view = LayoutInflater.from(this).inflate(R.layout.viewpager_item,null);
ImageView imageView = (ImageView) view.findViewById(R.id.viewpager_item_img);
if (i == 0){ //第一张的时候,把最后一张填充到第一张的前一张
imageView.setImageResource(images[images.length - 1]);
}else if (i == images.length + 1){
imageView.setImageResource(images[0]);
}else {
imageView.setImageResource(images[i - 1]);
}
mViews.add(view);
mViews2.add(view);
}
return mViews;
}
数据初始化之后,我们还需要在滑动的时候,不动声色的 切换,这样才能有一个轮播的效果,所以,切换的代码如下:
mViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE){
int currentpageid = mViewpager.getCurrentItem();
if (currentpageid == 0){
mViewpager.setCurrentItem(images.length,false);
} else if (currentpageid == (images.length +1)){
mViewpager.setCurrentItem(1,false);
}
}
}
});
完整代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "zsr";
//展示三张,其实是有五张
int[] images = new int[]{R.mipmap.beauty,R.mipmap.beuty1,R.mipmap.beauty2};
private ViewPager mViewpager;
private List<View> mViews = new ArrayList<>();
private List<View> mViews2 = new ArrayList<>();
private int lastPage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewpager = (ViewPager) findViewById(R.id.viewpager);
initImg();
mViewpager.setAdapter(new CusTomAdapter());
// mViewpager.setPageTransformer(false,new customTransFromer());
mViewpager.setOffscreenPageLimit(3);
mViewpager.setCurrentItem((images.length + 2)/2); //取中间的位置
mViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE){
int currentpageid = mViewpager.getCurrentItem();
if (currentpageid == 0){
mViewpager.setCurrentItem(images.length,false);
} else if (currentpageid == (images.length +1)){
mViewpager.setCurrentItem(1,false);
}
}
}
});
}
class CusTomAdapter extends PagerAdapter{
@Override
public int getCount() {
return mViews.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
//return super.instantiateItem(container, position);
container.addView(mViews.get(position));
return mViews.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mViews.get(position));
}
}
private List<View> initImg() {
for (int i = 0; i < images.length + 2; i++) {
View view = LayoutInflater.from(this).inflate(R.layout.viewpager_item,null);
ImageView imageView = (ImageView) view.findViewById(R.id.viewpager_item_img);
if (i == 0){ //第一张的时候,把最后一张填充到第一张的前一张
imageView.setImageResource(images[images.length - 1]);
}else if (i == images.length + 1){
imageView.setImageResource(images[0]);
}else {
imageView.setImageResource(images[i - 1]);
}
mViews.add(view);
mViews2.add(view);
}
return mViews;
}
}
效果呢,如下所示:
但这种方式,在快速滚动的时候,不是很利索,原因就是切换那里,我们是在滚动结束的时候在 切过去的,如果你在快速滚动,就有一种最后一张切到第一张,有卡顿的感觉。
2、getCount 中返回一个很大的数字,实现轮播
这种方式,就是在 pagerveiw 的 getCount 中,返回一个很大的数字,如下:
@Override
public int getCount() {
//设置一个很大的数,用来实现轮播效果,实际上可以不用这么大
return Integer.MAX_VALUE;
}
为了在 adapter 中获取到正确的值,把 position 取余即可。然后为了方便左右都能滑动,在初始化的时候,把 setcurrentitem 的值设置成中间即可。为了方便,这里新建一个类,继承 Viewpager:
public class BannerViewPager extends ViewPager {
private static final String TAG = "BannerViewPager";
private BannerCustomAdapter mBannerCustomAdapter;
public BannerViewPager(Context context) {
super(context);
}
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAdapter(List<View> views) {
setAdapter(new BannerViewAdapter(views));
}
class BannerViewAdapter extends PagerAdapter{
List<View> views;
public BannerViewAdapter(List<View> views) {
this.views = views;
}
@Override
public int getCount() {
//设置一个很大的数,用来实现轮播效果
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
//自己适配类型,比如imageview
View view = null;
if (this.views != null){
//对Viewpager页号求模去除View列表中要显示的项
position %= this.views.size();
//如果View已经在之前添加到了一个父组件,则必须先remove,否则会抛出IllegalStateException。
view = this.views.get(position);
ViewParent viewParent = view.getParent();
if (viewParent!=null){
parent.removeAllView(view);
}
}
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
//上面已经对 item remove了,这里如果再次remove,则实现不出来
// container.removeView(this.views.get(position % this.views.size()));
}
}
}
然后,替换一下xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:background="#22e12222"
tools:context="com.rachel.activitytest.MainActivity">
<com.rachel.commonsdk.custom.bannerview.BannerViewPager
android:layout_marginTop="20dp"
android:id="@+id/custom_viewpager"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
主函数这样写:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "zsr";
//展示三张,其实是有五张
int[] images = new int[]{R.mipmap.beauty,R.mipmap.beuty1,R.mipmap.beauty2};
private ViewPager mViewpager;
private List<View> mViews = new ArrayList<>();
private int lastPage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initImg();
BannerViewPager bannerViewPager = (BannerViewPager) findViewById(R.id.custom_viewpager);
bannerViewPager.setAdapter(mViews);
bannerViewPager.setPageTransformer(false,new customTransFromer());
bannerViewPager.setCurrentItem(900); //取中间或者大一点的数都可以,保证左边有item
}
private List<View> initImg() {
for (int i = 0; i < images.length + 2; i++) {
View view = LayoutInflater.from(this).inflate(R.layout.viewpager_item,null);
ImageView imageView = (ImageView) view.findViewById(R.id.viewpager_item_img);
mViews.add(view);
}
return mViews;
}
}
这样也实现了上面一样的效果。
当然,你可以加写 viewpager 的动画,比如魅族的 bannerview:
/**
* 魅族效果
*/
class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MAX_SCALE = 1.0f;
private static final float MIN_SCALE = 0.9f;//0.85f
@Override
public void transformPage(View view, float position) {
//setScaleY只支持api11以上
if (position < -1) {
// view.setScaleX(MIN_SCALE);
view.setScaleY(MIN_SCALE);
} else if (position <= 1) //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0
{ // [-1,1]
// Log.e("TAG", view + " , " + position + "");
float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
// view.setScaleX(scaleFactor);
//每次滑动后进行微小的移动目的是为了防止在三星的某些手机上出现两边的页面为显示的情况
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// view.setScaleX(MIN_SCALE);
view.setScaleY(MIN_SCALE);
}
}
}
在 viewpager 设置一下:
mViewpager.setPageTransformer(false,new ZoomOutPageTransformer());
当然,你需要在 xml中,设置一个 viewpager 左右的margin 和在根布局中添加 clipChildren = false:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:background="#22e12222"
tools:context="com.rachel.activitytest.MainActivity">
<android.support.v4.view.ViewPager
android:layout_marginTop="20dp"
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
/>
</LinearLayout>
效果如下:
轮播与封装
这个就比较简单了,用一个 Handle 就可以了,需要注意的是,在按下的时候,需要取消消息,在抬起之后再取恢复;而上面的这种做法,其实还是有点问题的,用户除了要配置数据之外,还有额外的 viewpager 的配置,还有底部小圆圈的配置,这样是需要配置天多,那能不能我们先封装起来,用户再用的时候,只要配置数据就可以了?
当然可以,这里我们新建一个 xml,包含 viewpager 和 底部导航圆点 banner_layout.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:clipChildren="false"
>
<android.support.v4.view.ViewPager
android:id="@+id/banner_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<LinearLayout
android:id="@+id/banner_indeticor"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="10dp"
/>
</RelativeLayout>
接着新建一个类,让它继承 releativelayout,然后把这个 banner_layout.xml ,加进来即可:
public class CusViewPager extends RelativeLayout implements ViewPager.OnPageChangeListener, View.OnTouchListener {
private ViewPager mViewPager;
private Context mContext;
private LinearLayout mLinearLayout;
public CusViewPager(Context context) {
this(context,null);
}
public CusViewPager(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CusViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
View view = LayoutInflater.from(context).inflate(R.layout.banner_layout,null);
addView(view); // 把包含 viewpager 和 底部 linearlayout的view 加给自身
mViewPager = (ViewPager) view.findViewById(R.id.banner_viewpager);
mLinearLayout = (LinearLayout) view.findViewById(R.id.banner_indeticor);
mViewPager.setOnTouchListener(this);
}
....
}
这样,我们在这里就可以操作 viewpager 和 底部小圆点的操作了。关于底部小圆点的,可以参考我的另一篇文章:
5分钟搞定开机引导界面
完整代码如下:
public class CusViewPager extends RelativeLayout implements ViewPager.OnPageChangeListener, View.OnTouchListener {
private static final String TAG = "zsr";
private ViewPager mViewPager;
private Context mContext;
private LinearLayout mLinearLayout;
private static final int LOOP_COUNT = 5000;
private static final int LOOP_START = 1;
private static final int LOOP_TIME = 1000;
private int mCurrentPageId;
private List<View> mPointLists = new ArrayList<>();
public CusViewPager(Context context) {
this(context,null);
}
public CusViewPager(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CusViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
View view = LayoutInflater.from(context).inflate(R.layout.banner_layout,null);
addView(view);
mViewPager = (ViewPager) view.findViewById(R.id.banner_viewpager);
mLinearLayout = (LinearLayout) view.findViewById(R.id.banner_indeticor);
mViewPager.setOnTouchListener(this);
}
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case LOOP_START:
//这里重新获取一下 id,防止手动滑动而导致位置错乱
mCurrentPageId = mViewPager.getCurrentItem();
if (mCurrentPageId > LOOP_COUNT /2 ){
mViewPager.setCurrentItem(++mCurrentPageId);
}
if (mCurrentPageId > LOOP_COUNT){
mCurrentPageId = LOOP_COUNT+1;
}
mHandler.sendEmptyMessageDelayed(LOOP_START,LOOP_COUNT);
break;
}
}
};
public void setDatas(List<View> lists){
if (mViewPager != null) {
mViewPager.setAdapter(new AutoAdapter(lists));
mViewPager.setCurrentItem(lists.size()+LOOP_COUNT/2);
mCurrentPageId = lists.size()+LOOP_COUNT/2;
initPoint(lists);
mViewPager.addOnPageChangeListener(this);
// setViewScrollTime(mContext,mViewPager,300);
mHandler.sendEmptyMessageDelayed(LOOP_START,LOOP_TIME);
Log.d(TAG, "handleMessage: "+System.currentTimeMillis());
}
}
private void initPoint(List<View> lists) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(15,0,0,0);
for (int i = 0; i < lists.size(); i++) {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(R.drawable.pointselector);
imageView.setLayoutParams(params);
if (i == 0){
imageView.setSelected(true);
}else{
imageView.setSelected(false);
}
mPointLists.add(imageView);
mLinearLayout.addView(imageView);
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
for (int i = 0; i < mPointLists.size(); i++) {
View view = mPointLists.get(i);
if (i == (position % mPointLists.size())){
view.setSelected(true);
}else{
view.setSelected(false);
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mHandler.removeMessages(LOOP_START);
break;
case MotionEvent.ACTION_UP:
//手指抬起3s后,仍没有再次按下,则启动轮播
mHandler.sendEmptyMessageDelayed(LOOP_START,3000);
break;
}
return false;
}
class AutoAdapter extends PagerAdapter {
List<View> datas;
public AutoAdapter(List<View> datas) {
this.datas = datas;
}
@Override
public int getCount() {
return datas.size() + LOOP_COUNT;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = datas.get(position % datas.size());
//Log.d(TAG, "instantiateItem: "+view);
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeAllViews();
}
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
/**
* 魅族效果
*/
class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MAX_SCALE = 1.0f;
private static final float MIN_SCALE = 0.9f;//0.85f
@Override
public void transformPage(View view, float position) {
//setScaleY只支持api11以上
if (position < -1) {
// view.setScaleX(MIN_SCALE);
view.setScaleY(MIN_SCALE);
} else if (position <= 1) //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0
{ // [-1,1]
// Log.e("TAG", view + " , " + position + "");
float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
// view.setScaleX(scaleFactor);
//每次滑动后进行微小的移动目的是为了防止在三星的某些手机上出现两边的页面为显示的情况
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// view.setScaleX(MIN_SCALE);
view.setScaleY(MIN_SCALE);
}
}
}
/**
* 渐隐效果
*/
class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
private void setViewScrollTime(Context context,ViewPager viewpager,int time){
FixedSpeedScroller mScroller = null;
try {
Field mField;
mField = ViewPager.class.getDeclaredField("mScroller");
mField.setAccessible(true);
mScroller = new FixedSpeedScroller(context,
new AccelerateInterpolator());
mScroller.setmDuration(time);
mField.set(viewpager, mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}
}
所以,在使用的时候,我们只要加载布局:
<com.rachel.studyapp.view.CusViewPager
android:id="@+id/autoview"
android:layout_width="match_parent"
android:layout_height="180dp"/>
在主函数中调用即可:
CusViewPager autoView = (CusViewPager) view.findViewById(R.id.autoview);
autoView.setDatas(mDatas);
这样就省去了很多的步骤了。效果如下: