Android之卡片式滑动
文章目录
- 前言
- 一、效果图
- 二、实现步骤
- 1.主界面xml
- 2.自定义的viewpage
- 3.卡片接口类
- 4.阴影和缩放变化类
- 5.卡片adapter
- 6.卡片adapter的xml
- 7.style
- 8.CardItem
- 9.activity实现
- 10.指示器drawable
- 总结
前言
对于这个需求,之前的项目也有做过,但是过于赶项目就没有放博客上,刚好这次又遇到这个需求,所以就记录一下,也希望能帮到正在实现此功能的朋友们少走一些弯路。
一、效果图
二、实现步骤
1.主界面xml
自定义的ViewPage和一个LinearLayout
<com.hzwl.aidigital.utils.CardViewPage
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backhsColor"
android:clipToPadding="false"
android:elevation="0dp"
android:overScrollMode="never"
android:paddingLeft="40dp"
android:paddingTop="50dp"
android:paddingRight="40dp"
android:paddingBottom="50dp" />
<LinearLayout
android:id="@+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal"></LinearLayout>
2.自定义的viewpage
代码如下(示例):
/**
* @Author : CaoLiulang
* @Time : 2025/3/22 15:21
* @Description :自定义ViewPager
*/
public class CardViewPage extends ViewPager {
private float mLastOffset;
private CardAdapter cardAdapter;
public CardViewPage(Context context) {
super(context);
}
public CardViewPage(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onPageScrolled(int position, float positionOffset, int offsetPixels) {
super.onPageScrolled(position, positionOffset, offsetPixels);
cardAdapter = (CardAdapter) getAdapter();
if (cardAdapter ==null)
{
return;
}
// If we're going backwards, onPageScrolled receives the last position
// instead of the current one
int realCurrentPosition;
int nextPosition;
float realOffset;
//positionOffset 如果往左边滑动就是逐渐变大 0->1 ,然后归0,如果往右滑动 1-》0 ,最后归0。
//下面这个判断区分左右,
boolean goingLeft = mLastOffset > positionOffset;
if (goingLeft) {
realCurrentPosition = position + 1;
nextPosition = position;
realOffset = 1 - positionOffset;
} else {
nextPosition = position + 1;
realCurrentPosition = position;
realOffset = positionOffset;
}
if (nextPosition > getAdapter().getCount() - 1
|| realCurrentPosition > cardAdapter.getCount() - 1) {
return;
}
CardView currentCard = cardAdapter.getCardViewAt(realCurrentPosition);
if (currentCard!=null)
{
float scclex=(float) (1 + 0.1 * (1 - realOffset));
float sccley=(float)(1 + 0.1 * (1 - realOffset));
currentCard.setScaleX(scclex);
currentCard.setScaleY(sccley);
currentCard.setCardElevation((cardAdapter.getBaseElevation() + cardAdapter.getBaseElevation()
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (1 - realOffset)));
}
CardView nextCard = cardAdapter.getCardViewAt(nextPosition);
// We might be scrolling fast enough so that the next (or previous) card
// was already destroyed or a fragment might not have been created yet
if (nextCard != null) {
nextCard.setScaleX((float) (1 + 0.1 * (realOffset)));
nextCard.setScaleY((float) (1 + 0.1 * (realOffset)));
nextCard.setCardElevation((cardAdapter.getBaseElevation() + cardAdapter.getBaseElevation()
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (realOffset)));
}
mLastOffset = positionOffset;
}
}
3.卡片接口类
/**
* @Author : CaoLiulang
* @Time : 2025/3/22 14:19
* @Description :卡片接口类
*/
public interface CardAdapter {
int MAX_ELEVATION_FACTOR = 5;
float getBaseElevation();
CardView getCardViewAt(int position);
int getCount();
}
4.阴影和缩放变化类
/**
* @Author : CaoLiulang
* @Time : 2025/3/22 14:18
* @Description :阴影和缩放变化类
*/
public class ShadowTransformer implements ViewPager.OnPageChangeListener, ViewPager.PageTransformer {
private ViewPager mViewPager;
private CardAdapter mAdapter;
private float mLastOffset;
private boolean mScalingEnabled;
public ShadowTransformer(ViewPager viewPager, CardAdapter adapter) {
mViewPager = viewPager;
viewPager.addOnPageChangeListener(this);
mAdapter = adapter;
}
public void enableScaling(boolean enable) {
if (mScalingEnabled && !enable) {
// shrink main card
CardView currentCard = mAdapter.getCardViewAt(mViewPager.getCurrentItem());
if (currentCard != null) {
currentCard.animate().scaleY(1);
currentCard.animate().scaleX(1);
}
}else if(!mScalingEnabled && enable){
// grow main card
CardView currentCard = mAdapter.getCardViewAt(mViewPager.getCurrentItem());
if (currentCard != null) {
currentCard.animate().scaleY(1.1f);
currentCard.animate().scaleX(1.1f);
}
}
mScalingEnabled = enable;
}
@Override
public void transformPage(View page, float position) {
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int realCurrentPosition;
int nextPosition;
float baseElevation = mAdapter.getBaseElevation();
float realOffset;
boolean goingLeft = mLastOffset > positionOffset;
// If we're going backwards, onPageScrolled receives the last position
// instead of the current one
if (goingLeft) {
realCurrentPosition = position + 1;
nextPosition = position;
realOffset = 1 - positionOffset;
} else {
nextPosition = position + 1;
realCurrentPosition = position;
realOffset = positionOffset;
}
// Avoid crash on overscroll
if (nextPosition > mAdapter.getCount() - 1
|| realCurrentPosition > mAdapter.getCount() - 1) {
return;
}
CardView currentCard = mAdapter.getCardViewAt(realCurrentPosition);
// This might be null if a fragment is being used
// and the views weren't created yet
if (currentCard != null) {
if (mScalingEnabled) {
currentCard.setScaleX((float) (1 + 0.1 * (1 - realOffset)));
currentCard.setScaleY((float) (1 + 0.1 * (1 - realOffset)));
}
currentCard.setCardElevation((baseElevation + baseElevation
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (1 - realOffset)));
}
CardView nextCard = mAdapter.getCardViewAt(nextPosition);
// We might be scrolling fast enough so that the next (or previous) card
// was already destroyed or a fragment might not have been created yet
if (nextCard != null) {
if (mScalingEnabled) {
nextCard.setScaleX((float) (1 + 0.1 * (realOffset)));
nextCard.setScaleY((float) (1 + 0.1 * (realOffset)));
}
nextCard.setCardElevation((baseElevation + baseElevation
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (realOffset)));
}
mLastOffset = positionOffset;
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
5.卡片adapter
/**
* @Author : CaoLiulang
* @Time : 2025/3/22 14:18
* @Description :卡片adapter
*/
public class CardPagerAdapter extends PagerAdapter implements CardAdapter {
private List<CardView> mViews;
private List<CardItem> mData;
private float mBaseElevation;
public CardPagerAdapter() {
mData = new ArrayList<>();
mViews = new ArrayList<>();
}
public void addCardItem(CardItem item) {
mViews.add(null);
mData.add(item);
}
public float getBaseElevation() {
return mBaseElevation;
}
@Override
public CardView getCardViewAt(int position) {
return mViews.get(position);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = LayoutInflater.from(container.getContext())
.inflate(R.layout.adapter, container, false);
container.addView(view);
bind(mData.get(position), view);
CardView cardView = view.findViewById(R.id.cardView);
if (mBaseElevation == 0) {
mBaseElevation = cardView.getCardElevation();
}
cardView.setMaxCardElevation(mBaseElevation * MAX_ELEVATION_FACTOR);
mViews.set(position, cardView);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mViews.set(position, null);
}
private void bind(CardItem item, View view) {
ImageView imag_back = view.findViewById(R.id.imag_back);
ImageView imag_logo = view.findViewById(R.id.imag_logo);
TextView text_ok = view.findViewById(R.id.text_ok);
if (item.getmTitleResource().equals("抖音")) {
imag_back.setImageResource(R.mipmap.cardy);
imag_logo.setImageResource(R.mipmap.ico_dy);
} else if (item.getmTitleResource().equals("快手")) {
imag_back.setImageResource(R.mipmap.carks);
imag_logo.setImageResource(R.mipmap.ico_ks);
} else if (item.getmTitleResource().equals("视频号")) {
imag_back.setImageResource(R.mipmap.carsph);
imag_logo.setImageResource(R.mipmap.ico_sph);
} else if (item.getmTitleResource().equals("小红书")) {
imag_back.setImageResource(R.mipmap.carxhs);
imag_logo.setImageResource(R.mipmap.ico_xhs);
} else if (item.getmTitleResource().equals("美团")) {
imag_back.setImageResource(R.mipmap.carmt);
imag_logo.setImageResource(R.mipmap.ico_mt);
} else if (item.getmTitleResource().equals("拼多多")) {
imag_back.setImageResource(R.mipmap.carpdd);
imag_logo.setImageResource(R.mipmap.ico_pdd);
} else if (item.getmTitleResource().equals("京东")) {
imag_back.setImageResource(R.mipmap.carjd);
imag_logo.setImageResource(R.mipmap.ico_jd);
} else if (item.getmTitleResource().equals("淘宝")) {
imag_back.setImageResource(R.mipmap.cartb);
imag_logo.setImageResource(R.mipmap.ico_tb);
} else if (item.getmTitleResource().equals("支付宝")) {
imag_back.setImageResource(R.mipmap.carzfb);
imag_logo.setImageResource(R.mipmap.ico_zbzfb);
} else if (item.getmTitleResource().equals("百度")) {
imag_back.setImageResource(R.mipmap.carbd);
imag_logo.setImageResource(R.mipmap.ico_bd);
}
text_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ToastUtils.showToast("点击了" + item.getmTitleResource());
}
});
}
}
6.卡片adapter的xml
这里一定要引用样式,不然会有阴影,然后有边框线,试过xml各种设置都不行,必须要样式
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
style="@style/CustomCardView"
app:cardElevation="0dp"
app:cardUseCompatPadding="false">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="270dp"
android:layout_height="498dp"
android:layout_centerHorizontal="true">
<ImageView
android:id="@+id/imag_back"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@mipmap/cardy" />
<ImageView
android:id="@+id/imag_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:src="@mipmap/ico_dy"/>
<TextView
android:id="@+id/text_ok"
android:layout_width="200dp"
android:layout_height="54dp"
android:layout_below="@+id/imag_back"
android:layout_centerHorizontal="true"
android:layout_marginTop="-70dp"
android:gravity="center"
android:textColor="#FF4400"
android:textSize="14dp"
android:textStyle="bold" />
</RelativeLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
7.style
<style name="CustomCardView" parent="CardView">
<item name="cardBackgroundColor">#020D1B</item>
<item name="cardElevation">0dp</item>
<item name="cardCornerRadius">8dp</item>
</style>
8.CardItem
/**
* @Author : CaoLiulang
* @Time : 2025/3/22 14:19
* @Description :卡片Javabean
*/
public class CardItem {
private String mTitleResource;
public CardItem(String title) {
mTitleResource = title;
}
public String getmTitleResource() {
return mTitleResource;
}
public void setmTitleResource(String mTitleResource) {
this.mTitleResource = mTitleResource;
}
}
9.activity实现
1.自定义变量
private lateinit var imag_fh:ImageView
private lateinit var text_title:TextView
private lateinit var viewPager: CardViewPage
private lateinit var mCardAdapter: CardPagerAdapter
private lateinit var mCardShadowTransformer: ShadowTransformer
private lateinit var linear: LinearLayout
private lateinit var list: MutableList<String>
private lateinit var view: View
private var mNum = 2
2.代码部分
list = mutableListOf()
list.add("抖音")
list.add("快手")
list.add("视频号")
list.add("拼多多")
list.add("京东")
list.add("淘宝")
list.add("支付宝")
list.add("百度")
list.add("美团")
list.add("小红书")
imag_fh=findViewById(R.id.imag_fh)
text_title=findViewById(R.id.text_title)
text_title.text="选择直播平台"
imag_fh.setOnClickListener(this)
linear = findViewById(R.id.linear)
viewPager = findViewById(R.id.viewPager)
mCardAdapter = CardPagerAdapter()
for (i in list.indices) {
mCardAdapter.addCardItem(CardItem(list[i]))
//创建底部指示器(小圆点)
view = View(this)
view.setBackgroundResource(R.drawable.background)
view.isEnabled = false
//设置宽高
val layoutParams = LinearLayout.LayoutParams(30, 30)
//设置间隔
if (i != 0) {
layoutParams.leftMargin = 10
}
//添加到LinearLayout
linear.addView(view, layoutParams)
}
mCardShadowTransformer = ShadowTransformer(viewPager, mCardAdapter)
mCardShadowTransformer.enableScaling(true)
viewPager.setAdapter(mCardAdapter)
viewPager.setPageTransformer(false, mCardShadowTransformer)
//预加载几个界面
viewPager.setOffscreenPageLimit(5)
//默认显示第三个界面
viewPager.setCurrentItem(2)
//第一次显示小白点
linear.getChildAt(2).setEnabled(true)
//注册
viewPager.addOnPageChangeListener(object : OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
linear.getChildAt(mNum).setEnabled(false)
linear.getChildAt(position).setEnabled(true)
mNum = position
}
override fun onPageScrollStateChanged(state: Int) {
}
})
10.指示器drawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/enable" android:state_enabled="true" />
<item android:drawable="@drawable/disable" android:state_enabled="false" />
</selector>
1.enable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!--白色-->
<solid android:color="#ffffff" />
<!--半径-->
<corners android:radius="10dp" />
</shape>
2.disable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!--灰色-->
<solid android:color="#807E7E" />
<!--半径-->
<corners android:radius="10dp" />
</shape>
总结
以上就是整个卡片滑动效果的实现步骤和代码,是不是很简单,欢迎各位点评指正。