Android 一个无限循环滚动的卡片式ViewPager
Posted BandaYung
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 一个无限循环滚动的卡片式ViewPager相关的知识,希望对你有一定的参考价值。
前段时间做了一个关于商城的分享介绍卡片式ViewPager,效果看起来还是蛮炫丽的,整体有如下效果特点:
- 无限轮播
- 两种卡片布局(中间与两边的不同)
- 指示灯
- 滚动到下一个卡片会在Y轴进行偏移
- 可显示前后卡片的一部分
- 一开始进入从中间开始
- 卡片圆角,背景可颜色或图片
说的这么多估计大家还是有点懵吧,那我们就先看下效果图跟项目结构图,再一一跟大家解释其效果实现与文件作用。
实现这个不一样的卡片Viewpager注意的地方:
1、对ViewPager设置setPageMargin(int marginPixels)
2、布局中引用自定义的ViewPager进行设置clipToPadding=”false”,paddingLeft,paddingRight
3、对ViewPager设置setPageTransformer()切换时的动画效果
解析以上三个设置特点:
1、setPageMargin:一般ViewPager都是页与页在切换时都是紧贴在一起的。它的设置主要是使页与页有一定的margin。设置之后的效果:
2、clipToPadding: 这个属性一般都是viewgrounp对象才会用到, 他的意思就是对于padding 所占的尺寸大小也绘制其他的item的view。他必须与padding一起使用才会有作用。
clipToPadding=“true”或没有设置时的效果:
看到第一张,是不是明显的左右两张卡片没有显示出预览,再看到第二张才知道原来是左右两边有边距遮盖了,所以只有设置false才可以看到我们要的那种效果。
源码下载
一、类包文件的介绍:
1、bean->ShareCardItem:是通过用来解析数据的一个模型
2、util->StreamUtils:是用来读取文件res->raw文件的数据。
因为这里的卡片数据我这里就不做数据网络请求,防止项目域名变化,大家就大概理解data.json就是通过网络获取来的数据就行了。而该工具类就是通过get(Context context, int id)读取data.json中的数据json字符串。
public class StreamUtils
public static String get(Context context, int id)
InputStream stream = context.getResources().openRawResource(id);
return read(stream);
public static String read(InputStream stream)
return read(stream, "utf-8");
public static String read(InputStream is, String encode)
if (is != null)
try
BufferedReader reader = new BufferedReader(new InputStreamReader(is, encode));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null)
sb.append(line + "\\n");
is.close();
return sb.toString();
catch (UnsupportedEncodingException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
return "";
public static String getBase64(byte[] base)
String base64 = null;
try
base64 = Base64.encodeToString(base, Base64.NO_WRAP);
catch (Exception e)
e.printStackTrace();
return base64;
3、view->LoopPagerAdapterWrapper/LoopViewPager/ViewPager
本文的卡片无限循环滚动就是通过这上个文件实现的,代码量太多了,这里就不做粘贴,可以下载源码查看。
4、view->ScaleTransformer:是ViewPager中的卡片在Y轴上的移动动画。
注释掉的那些是切换卡片缩放跟透明度的变化,这里没做具体计算,如果要用的话,可以自行计算。
public class ScaleTransformer implements ViewPager.PageTransformer
private static final float MIN_SCALE = 0.7f;//缩放的比例
private static final float MIN_ALPHA = 0.5f;//透明度的比例
private static final float MOVE_Y = 40;//设置y轴移动的基数
@Override
public void transformPage(View page, float position)
if (position < -1 || position > 1)
// page.setAlpha(MIN_ALPHA);
// page.setScaleX(MIN_SCALE);
// page.setScaleY(MIN_SCALE);
page.setTranslationY(0);
else if (position <= 1) // [-1,1]
// float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
if (position <= 0)
// float scaleX = 1 + 0.3f * position;
// page.setScaleX(scaleX);
// page.setScaleY(scaleX);
page.setTranslationY(0);
else
page.setTranslationY(-MOVE_Y * (1 - Math.abs(position)));
// float scaleX = 1 - 0.3f * position;
// page.setScaleX(scaleX);
// page.setScaleY(scaleX);
// page.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
5、view->ShareCardView:重点来了,这就是所说的卡片ViewPager
这里面用到了两种卡片,所以这里只是为了给大家举例,如果要实现其他效果,大致也是一样的。
public class ShareCardView extends FrameLayout implements ViewPager.OnPageChangeListener
private static final int MSG_NEXT = 1;
private Context mContext;
private ViewPager mViewPager; //自定义的无限循环ViewPager
private ViewGroup mViewGroup;
private CardAdapter mAdapter;
private int mFocusImageId;
private int mUnfocusImageId;
private Handler mHandler;
private TimerTask mTimerTask;
private Timer mTimer;
private int centerPos; //中间卡片位置
private int pageCount; //所有卡片的个数
public ShareCardView(Context context)
this(context, null);
public ShareCardView(Context context, AttributeSet set)
super(context, set);
init(context);
private void init(Context context)
mContext = context;
View container = LayoutInflater.from(mContext).inflate(R.layout.layout_share_cardview, null);
addView(container);
mViewPager = (ViewPager) (container.findViewById(R.id.slide_viewPager));
mViewGroup = (ViewGroup) (container.findViewById(R.id.slide_viewGroup));
mFocusImageId = R.mipmap.card_point_focused;
mUnfocusImageId = R.mipmap.card_point_unfocused;
mViewPager.setPageTransformer(false, new ScaleTransformer());//设置卡片切换动画
mViewPager.setPageMargin(60);//卡片与卡片间的距离
mViewPager.setOnPageChangeListener(this);
mHandler = new Handler()
@Override
public void handleMessage(Message msg)
switch (msg.what)
case MSG_NEXT:
next();
break;
super.handleMessage(msg);
;
mViewPager.setOnTouchListener(new OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
stopTimer();
break;
case MotionEvent.ACTION_UP:
startTimer();
break;
return false;
);
//设置数据
public void setCardData(ShareCardItem cardItem)
pageCount = cardItem.getDataList().size() + 1;
centerPos = pageCount / 2;//中间卡片的位置
mAdapter = new CardAdapter(cardItem);
mViewPager.setAdapter(mAdapter);
mAdapter.select(centerPos);//默认从中间卡片开始
mViewPager.setCurrentItem(centerPos);
mViewPager.setOffscreenPageLimit(pageCount);//预加载所有卡片
startTimer();
//启动动画
public void startTimer()
stopTimer();
mTimerTask = new TimerTask()
@Override
public void run()
mHandler.sendEmptyMessage(MSG_NEXT);
;
mTimer = new Timer(true);
mTimer.schedule(mTimerTask, 3000, 3000);
//停止动画
public void stopTimer()
mHandler.removeMessages(MSG_NEXT);
if (mTimer != null)
mTimer.cancel();
mTimer = null;
if (mTimerTask != null)
mTimerTask.cancel();
mTimerTask = null;
//选择下一个卡片
public void next()
int pos = mViewPager.getCurrentItem();
pos += 1;
mViewPager.setCurrentItem(pos);
//判断是否显隐控件
public void refresh()
if (getCount() <= 0)
this.setVisibility(View.GONE);
else
this.setVisibility(View.VISIBLE);
public int getCount()
if (mAdapter != null)
return mAdapter.getCount();
return 0;
public class CardAdapter extends PagerAdapter
private LayoutInflater inflater;
private ArrayList<ImageView> mPoints;
private ZSCardItem zsCardItem = new ZSCardItem();
private List<LRCardItem> lrCardItems = new ArrayList<>();
private List<Integer> iconLists = Arrays.asList(R.mipmap.share_card1, R.mipmap.share_card2,
R.mipmap.share_card3, R.mipmap.share_card4);
public CardAdapter(ShareCardItem cardItem)
inflater = LayoutInflater.from(mContext);
mPoints = new ArrayList<>();
zsCardItem = cardItem.getData();
lrCardItems = cardItem.getDataList();
setItems();
@Override
public int getCount()
return pageCount;
private ImageView newPoint()
ImageView imageView = new ImageView(mContext);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.leftMargin = 20;
imageView.setLayoutParams(params);
imageView.setBackgroundResource(mUnfocusImageId);
return imageView;
//中间卡片
public void setZSCard(View view, ZSCardItem item)
TextView zsTv = (TextView) view.findViewById(R.id.zs_tv);
TextView jyeTv = (TextView) view.findViewById(R.id.jye_tv);
TextView lmTv = (TextView) view.findViewById(R.id.lm_tv);
TextView sbTv = (TextView) view.findViewById(R.id.sb_tv);
zsTv.setText(String.valueOf(item.getTodayIndex()));
jyeTv.setText(String.format("交易额:%s元", item.getAmount()));
lmTv.setText(String.format("%s家", item.getShopNum()));
sbTv.setText(String.format("%s个", item.getMemberNum()));
//其他卡片
public void setLRCard(View view, int lrCardItemPos)
LRCardItem item = lrCardItems.size() > lrCardItemPos ?
lrCardItems.get(lrCardItemPos) : new LRCardItem();
ImageView iconIv = (ImageView) view.findViewById(R.id.icon_iv);
TextView titleTv = (TextView) view.findViewById(R.id.title_tv);
TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
if (lrCardItemPos < iconLists.size())
iconIv.setImageResource(iconLists.get(lrCardItemPos));
titleTv.setText(item.getTitle());
contentTv.setText(item.getContent());
contentTv.setMovementMethod(ScrollingMovementMethod.getInstance());
public void setItems()
while (mPoints.size() < pageCount) mPoints.add(newPoint());
while (mPoints.size() > pageCount) mPoints.remove(0);
mViewGroup.removeAllViews();
for (ImageView view : mPoints)
mViewGroup.addView(view);
mViewPager.setCurrentItem(0);
select(0);
public void select(int pos)
if (mPoints.size() > 0)
pos = pos % mPoints.size();
for (int i = 0; i < mPoints.size(); i++)
if (i == pos)
mPoints.get(i).setBackgroundResource(mFocusImageId);
else
mPoints.get(i).setBackgroundResource(mUnfocusImageId);
refresh();
@Override
public boolean isViewFromObject(View view, Object object)
return view == object;
@Override
public Object instantiateItem(ViewGroup container, int position)
View view = null;
if (position == centerPos)
view = inflater.inflate(R.layout.layout_share_zscard, container, false);
setZSCard(view, zsCardItem);
else if (position < centerPos)
view = inflater.inflate(R.layout.layout_share_lrcard, container, false);
setLRCard(view, position);
else
view = inflater.inflate(R.layout.layout_share_lrcard, container, false);
setLRCard(view, position - 1);
container.addView(view);
return view;
@Override
public void destroyItem(ViewGroup container, int position, Object object)
container.removeView((View) object);
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
@Override
public void onPageSelected(int position)
mAdapter.select(position);
@Override
public void onPageScrollStateChanged(int state)
二、布局文件介绍:
1、卡片viewpager+指示灯:layout_share_cardview.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="wrap_content">
<soban.cardloopviewpager.view.ViewPager
android:id="@+id/slide_viewPager"
android:layout_width="match_parent"
android:layout_height="@dimen/share_card_height"
android:clipToPadding="false"
android:paddingBottom="5dp"
android:paddingLeft="40dp"
android:paddingRight="40dp"
android:paddingTop="30dp" />
<LinearLayout
android:id="@+id/slide_viewGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/slide_viewPager"
android:gravity="center_horizontal"
android:orientation="horizontal" />
</RelativeLayout>
2、中间卡片+圆角+背景图:layout_share_zscard.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/subview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_share_card">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="1dip"
android:background="@mipmap/share_card_zs" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/share_card_space">
<TextView
android:id="@+id/zs_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dip"
android:text="0"
android:textColor="@color/theme_color"
android:textSize="36sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/zs_tv"
android:layout_centerHorizontal="true"
android:text="今日猫鸽指数"
android:textSize="18sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/share_card_c" />
<LinearLayout
android:id="@+id/bottom_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/jye_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/share_card_space"
android:text="交易额:0元"
android:textSize="@dimen/share_card_text" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="@color/theme_border" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/share_card_space">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/lm_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0家"
android:textSize="@dimen/share_card_text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="联盟商家"
android:textColor="@color/theme_text"
android:textSize="@dimen/share_card_text" />
</LinearLayout>
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:background="@color/theme_border" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/sb_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0个"
android:textSize="@dimen/share_card_text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="猫鸽使者"
android:textColor="@color/theme_text"
android:textSize="@dimen/share_card_text" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
3、左右卡片+圆角+背景色:layout_share_lrcard.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/subview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_share_card"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="@dimen/share_card_space">
<ImageView
android:id="@+id/icon_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/share_card1" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="@dimen/share_card_space"
android:background="@color/theme_border" />
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dip"
android:layout_marginTop="10dip"
android:singleLine="true"
android:textColor="@color/theme_color"
android:textSize="@dimen/share_card_text" />
<TextView
android:id="@+id/content_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.5"
android:maxLines="9"
android:scrollbars="none"
android:textSize="@dimen/share_card_text" />
</LinearLayout>
三、注意build.gradle引用的库:
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.google.code.gson:gson:2.6.2'
butterknife:控件对象自动创建出来
gson:生成javabean需要
借鉴:http://blog.csdn.net/u012702547/article/details/52334161?locationNum=8
以上是关于Android 一个无限循环滚动的卡片式ViewPager的主要内容,如果未能解决你的问题,请参考以下文章
Android RecyclerView And CardView
一个卡片式的ViewPager,带你玩转ViewPager的PageTransformer属性!