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.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>

总结

以上就是整个卡片滑动效果的实现步骤和代码,是不是很简单,欢迎各位点评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/73589.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(UI自动化测试web端)第二篇:元素定位的方法_css定位之css选择器

看代码里的【find_element_by_css_selector( )】( )里的表达式怎么写&#xff1f; 文章介绍了第三种写法css选择器&#xff0c;你要根据网页中的实际情况来判断自己到底要用哪一种方法来进行元素定位。每种方法都要多练习&#xff0c;全都熟了之后你在工作当中使用起来元素定位…

使用vscode搭建pywebview集成vue项目示例

文章目录 前言环境准备项目源码下载一、项目说明1 目录结构2 前端项目3 后端项目获取python安装包(选择对应版本及系统) 三、调试与生成可执行文件1 本地调试2 打包应用 四、核心代码说明1、package.json2、vite.config.ts设置3、main.py后端入口文件说明 参考文档 前言 本节我…

C stm32f10x LED亮

#include<stm32f10x.h>int main(){#if 0 //APIOA 时钟初始化unsigned int * p(unsigned int*)0x40021018;*p | 0x1<<2;//A0 推挽输出p(unsigned int*)0x40010800;*p *p & ~0xf | 0x1;//A0低电平p(unsigned int*)0x4001080c;*p & ~0x1;#endifRCC->APB2E…

redux ,react-redux,redux-toolkit 简单总结

Redux、React-Redux 和 Redux Toolkit 是协同工作的三个库&#xff0c;各自承担不同角色&#xff0c;相互协同。 Redux&#xff1a;基础底座 底层状态管理库&#xff0c;负责状态存储、Action 派发和 Reducer 执行 ​React-Redux&#xff1a;连接 React 组件与 Redux Store 通…

智能制造:物联网和自动化之间的关系

工业自动化 工业自动化是机器设备或生产过程在不需要人工直接干预的情况下按预期的目标实现测量、操纵等信息处理和过程控制的统称。 在传统的工业生产过程中&#xff0c;很多环节需要人工操作&#xff0c;比如设备调试、生产监控、质量检测等。然而&#xff0c;随着工业自动化…

“自动驾驶背后的数学” 专栏导读

专栏链接&#xff1a; 自动驾驶背后的数学 专栏以“自动驾驶背后的数学”为主题&#xff0c;从基础到深入&#xff0c;再到实际应用和未来展望&#xff0c;全面解析自动驾驶技术中的数学原理。开篇用基础数学工具搭建自动驾驶的整体框架&#xff0c;吸引儿童培养兴趣&#xff0…

集成学习(下):Stacking集成方法

一、Stacking的元学习革命 1.1 概念 Stacking&#xff08;堆叠法&#xff09; 是一种集成学习技术&#xff0c;通过组合多个基学习器&#xff08;base learner&#xff09;的预测结果&#xff0c;并利用一个元模型&#xff08;meta-model&#xff09;进行二次训练&#xff0c…

Dubbo 全面解析:从 RPC 核心到服务治理实践

一、分布式系统与 RPC 框架概述 在当今互联网时代&#xff0c;随着业务规模的不断扩大&#xff0c;单体架构已经无法满足高并发、高可用的需求&#xff0c;分布式系统架构成为主流选择。而在分布式系统中&#xff0c;远程服务调用&#xff08;Remote Procedure Call&#xff0…

vmware虚拟机突然连不上网

1.一般是自己的主机把服务给关掉了&#xff0c;右击我的电脑&#xff0c;然后找到管理->服务&#xff0c;确保下面虚拟机的网络服务是否打开 Vmware虚拟机突然连接不上网络【方案集合】_vmware虚拟机连不上网-CSDN博客 2.识别到无效网络 控制面板->网络和共享中心&…

Selenium之简介

Selenium简介 首先&#xff0c;让我们看看官网是怎么定义的 Selenium是一个支持web浏览器自动化的一系列工具和库的综合项目&#xff0c;提供了扩展来模拟用户和浏览器的交互&#xff0c;用于扩展浏览器分配的分发服务器&#xff1b;用于W3C WebDriver规范的基础架构 其实&a…

SpringBoot 开发入门—Springboot基础框架汇总

一、环境准备 Java&#xff1a;Spring Boot 3.0.2 需要 Java 17&#xff0c;并且与 Java 19 兼容 Maven&#xff1a;Apache Maven 3.5 或更高版本兼容 二、启动器 以下应用程序启动器由 Spring Boot 在该组下提供&#xff1a;org.springframework.boot 表 1.Spring 引导应…

前端批量导入方式

webpack批量导入 webpack中使用 require.context 实现自动导入 const files require.context(./modules, false, /\.ts$/); const modules {}; files.keys().forEach((key) > {if (key ./index.ts) { return; }modules[key.replace(/(\.\/|\.ts)/g, )] files(key).def…

阿里巴巴1688类网站高保真原型设计

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>1688类B2B平台原型</title><script src…

C++设计模式-装饰模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析

一、装饰模式基本介绍 装饰模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;允许你在不改变对象自身的基础上&#xff0c;动态地给一个对象添加额外的职责。这种模式创建了一个装饰类&#xff0c;用来包装原有的类&#xff0c;并在保持类方法签…

2、学习Docker前置操作

docker三件套&#xff1a;镜像、容器、仓库 Docker hubhub.docker.com ubuntu安装【待更新】 CentOS安装 CentOS 仅发行版本中的内核支持 Docker。Docker 运行在 CentOS 7 (64-bit)上&#xff0c;要求系统为 64 位、Linux 系统内核版本为 3.8 以上&#xff0c;这里选用 Cen…

70. Linux驱动开发与裸机开发区别,字符设备驱动开发

一、裸机驱动开发回顾 1、底层&#xff0c;跟寄存器打交道&#xff0c;有些MCU提供了库。 二、Linux驱动开发思维 1、Linux下驱动开发直接操作寄存器不现实。 2、根据Linux下的各种驱动框架进行开发。一定要满足框架&#xff0c;也就是Linux下各种驱动框架的掌握。 3、驱动最…

【JavaScript 简明入门教程】为了Screeps服务的纯JS入门教程

0 前言 0-1 Screeps: World 众所不周知&#xff0c;​Screeps: World是一款面向编程爱好者的开源大型多人在线即时战略&#xff08;MMORTS&#xff09;沙盒游戏&#xff0c;其核心机制是通过编写JavaScript代码来控制游戏中的单位&#xff08;称为“Creep”&#xff09;&#…

第12章:优化并发_《C++性能优化指南》notes

优化并发 一、并发基础与优化核心知识点二、关键代码示例与测试三、关键优化策略总结四、性能测试方法论多选题设计题答案与详解多选题答案&#xff1a; 设计题答案示例 一、并发基础与优化核心知识点 线程 vs 异步任务 核心区别&#xff1a;std::thread直接管理线程&#xf…

[C++面试] RAII资源获取即初始化(重点)

一、入门 1、什么是 RAII&#xff1f; RAII&#xff08;Resource Acquisition Is Initialization&#xff0c;资源获取即初始化&#xff09;是 C 的核心编程范式&#xff0c;核心思想是 ​将资源的生命周期与对象的生命周期绑定&#xff1a; ​资源获取&#xff1a;在对象构造…

Unity粒子系统

目录 一、界面参数介绍1.主模块2.Emission 模块3.Shape 模块4.Velocity over Lifetime 模块5.Noise 模块6.Limit Velocity Over Lifetime 模块7.Inherit Velocity 模块8.Force Over Lifetime 模块9.Color Over Lifetime 模块10.Color By Speed 模块11.Size over Lifetime 模块1…