文章目錄
- 前言
- 一、效果圖
- 二、實現步驟
- 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.CardViewPageandroid: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" /><LinearLayoutandroid: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);}@Overrideprotected 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 oneint 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 yetif (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 cardCardView currentCard = mAdapter.getCardViewAt(mViewPager.getCurrentItem());if (currentCard != null) {currentCard.animate().scaleY(1);currentCard.animate().scaleX(1);}}else if(!mScalingEnabled && enable){// grow main cardCardView currentCard = mAdapter.getCardViewAt(mViewPager.getCurrentItem());if (currentCard != null) {currentCard.animate().scaleY(1.1f);currentCard.animate().scaleX(1.1f);}}mScalingEnabled = enable;}@Overridepublic void transformPage(View page, float position) {}@Overridepublic 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 oneif (goingLeft) {realCurrentPosition = position + 1;nextPosition = position;realOffset = 1 - positionOffset;} else {nextPosition = position + 1;realCurrentPosition = position;realOffset = positionOffset;}// Avoid crash on overscrollif (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 yetif (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 yetif (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;}@Overridepublic void onPageSelected(int position) {}@Overridepublic 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;}@Overridepublic CardView getCardViewAt(int position) {return mViews.get(position);}@Overridepublic int getCount() {return mData.size();}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><RelativeLayoutandroid:layout_width="270dp"android:layout_height="498dp"android:layout_centerHorizontal="true"><ImageViewandroid:id="@+id/imag_back"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@mipmap/cardy" /><ImageViewandroid: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"/><TextViewandroid: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:ImageViewprivate lateinit var text_title:TextViewprivate lateinit var viewPager: CardViewPageprivate lateinit var mCardAdapter: CardPagerAdapterprivate lateinit var mCardShadowTransformer: ShadowTransformerprivate lateinit var linear: LinearLayoutprivate lateinit var list: MutableList<String>private lateinit var view: Viewprivate var mNum = 22.代碼部分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}//添加到LinearLayoutlinear.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>
總結
以上就是整個卡片滑動效果的實現步驟和代碼,是不是很簡單,歡迎各位點評指正。