Android实现3D层叠式卡片图片展示
作者:倔强的菜鸟 发布时间:2022-04-22 16:39:19
标签:Android,图片展示
本文实例为大家分享了Android实现3D层叠式卡片图片展示的具体代码,供大家参考,具体内容如下
先看效果
好了效果看了,感兴趣的往下看哦!
整体实现思路
1、重写RelativeLayout 实现 锁定宽高比例的 RelativeLayout
2、自定义一个支持滑动的面板 继承 ViewGroup
3、卡片View绘制
4、页面中使用布局
首先为了更好的展示图片我们重写一下 RelativeLayout 编写一个锁定宽高比例的 RelativeLayout
AutoScaleRelativeLayout
public class AutoScaleRelativeLayout extends RelativeLayout {
//宽高比例
private float widthHeightRate = 0.35f;
public AutoScaleRelativeLayout(Context context) {
this(context, null);
}
public AutoScaleRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoScaleRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//通过布局获取宽高比例
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.card, 0, 0);
widthHeightRate = a.getFloat(R.styleable.card_widthHeightRate, widthHeightRate);
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 调整高度
int width = getMeasuredWidth();
int height = (int) (width * widthHeightRate);
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = height;
setLayoutParams(lp);
}
}
这样我们就编写好了我们想要的父布局
使用方法
<com.petterp.toos.ImageCard.AutoScaleRelativeLayout
android:id="@+id/card_top_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card:widthHeightRate="0.6588">
<!-- widthHeightRate:就是设置宽高的百分比-->
<ImageView
android:id="@+id/card_image_view"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
<!-- 这是我们展示的图片-->
<View
android:id="@+id/maskView"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:clickable="true" />
<!-- 这个是为了让我们图片上有波纹-->
</com.petterp.toos.ImageCard.AutoScaleRelativeLayout>
接下来就是主要布局,也就是展示图片的布局了
为了实现滑动我们编写一个支持滑动的画板
//事件处理
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
// 按下时保存坐标信息
if (action == MotionEvent.ACTION_DOWN) {
this.downPoint.x = (int) ev.getX();
this.downPoint.y = (int) ev.getY();
}
return super.dispatchTouchEvent(ev);
}
/* touch事件的拦截与处理都交给mDraghelper来处理 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
boolean moveFlag = moveDetector.onTouchEvent(ev);
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN的时候就对view重新排序
if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING) {
mDragHelper.abort();
}
orderViewStack();
// 保存初次按下时arrowFlagView的Y坐标
// action_down时就让mDragHelper开始工作,否则有时候导致异常
mDragHelper.processTouchEvent(ev);
}
return shouldIntercept && moveFlag;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
try {
// 统一交给mDragHelper处理,由DragHelperCallback实现拖动效果
// 该行代码可能会抛异常,正式发布时请将这行代码加上try catch
mDragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
//计算
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
allWidth = getMeasuredWidth();
allHeight = getMeasuredHeight();
}
//定位
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// 布局卡片view
int size = viewList.size();
for (int i = 0; i < size; i++) {
View viewItem = viewList.get(i);
int childHeight = viewItem.getMeasuredHeight();
int viewLeft = (getWidth() - viewItem.getMeasuredWidth()) / 2;
viewItem.layout(viewLeft, itemMarginTop, viewLeft + viewItem.getMeasuredWidth(), itemMarginTop + childHeight);
int offset = yOffsetStep * i;
float scale = 1 - SCALE_STEP * i;
if (i > 2) {
// 备用的view
offset = yOffsetStep * 2;
scale = 1 - SCALE_STEP * 2;
}
viewItem.offsetTopAndBottom(offset);
viewItem.setScaleX(scale);
viewItem.setScaleY(scale);
}
// 布局底部按钮的View
if (null != bottomLayout) {
int layoutTop = viewList.get(0).getBottom() + bottomMarginTop;
bottomLayout.layout(left, layoutTop, right, layoutTop
+ bottomLayout.getMeasuredHeight());
}
// 初始化一些中间参数
initCenterViewX = viewList.get(0).getLeft();
initCenterViewY = viewList.get(0).getTop();
childWith = viewList.get(0).getMeasuredWidth();
}
//onFinishInflate 当View中所有的子控件均被映射成xml后触发
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 渲染完成,初始化卡片view列表
viewList.clear();
int num = getChildCount();
for (int i = num - 1; i >= 0; i--) {
View childView = getChildAt(i);
if (childView.getId() == R.id.card_bottom_layout) {
bottomLayout = childView;
initBottomLayout();
} else {
// for循环取view的时候,是从外层往里取
CardItemView viewItem = (CardItemView) childView;
viewItem.setParentView(this);
viewItem.setTag(i + 1);
viewItem.maskView.setOnClickListener(btnListener);
viewList.add(viewItem);
}
}
CardItemView bottomCardView = viewList.get(viewList.size() - 1);
bottomCardView.setAlpha(0);
}
卡片View绘制
private void initSpring() {
SpringConfig springConfig = SpringConfig.fromBouncinessAndSpeed(15, 20);
SpringSystem mSpringSystem = SpringSystem.create();
springX = mSpringSystem.createSpring().setSpringConfig(springConfig);
springY = mSpringSystem.createSpring().setSpringConfig(springConfig);
springX.addListener(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
int xPos = (int) spring.getCurrentValue();
setScreenX(xPos);
parentView.onViewPosChanged(CardItemView.this);
}
});
springY.addListener(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
int yPos = (int) spring.getCurrentValue();
setScreenY(yPos);
parentView.onViewPosChanged(CardItemView.this);
}
});
}
//装载数据
public void fillData(CardDataItem itemData) {
Glide.with(getContext()).load(itemData.imagePath).into(imageView);
}
/**
* 动画移动到某个位置
*/
public void animTo(int xPos, int yPos) {
setCurrentSpringPos(getLeft(), getTop());
springX.setEndValue(xPos);
springY.setEndValue(yPos);
}
/**
* 设置当前spring位置
*/
private void setCurrentSpringPos(int xPos, int yPos) {
springX.setCurrentValue(xPos);
springY.setCurrentValue(yPos);
}
接下来我们需要使用它 编写Fragment布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:card="http://schemas.android.com/apk/res-auto"
android:background="#fff"
android:orientation="vertical">
<com.petterp.toos.ImageCard.CardSlidePanel
android:id="@+id/image_slide_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
card:bottomMarginTop="38dp"
card:itemMarginTop="10dp"
card:yOffsetStep="26dp">
<LinearLayout
android:id="@+id/card_bottom_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:background="#03A9F4"
android:text="右侧移除"
android:id="@+id/card_left_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button
android:background="#03A9F4"
android:text="右侧移除"
android:id="@+id/card_right_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
<com.petterp.toos.ImageCard.CardItemView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.petterp.toos.ImageCard.CardItemView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.petterp.toos.ImageCard.CardItemView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.petterp.toos.ImageCard.CardItemView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.petterp.toos.ImageCard.CardSlidePanel>
</LinearLayout>
代码中的使用
private void initView(View rootView) {
CardSlidePanel slidePanel = (CardSlidePanel) rootView
.findViewById(R.id.image_slide_panel);
cardSwitchListener = new CardSlidePanel.CardSwitchListener() {
@Override
public void onShow(int index) {
Toast.makeText(getContext(), "CardFragment"+"正在显示=" +index, Toast.LENGTH_SHORT).show();
}
//type 0=右边 ,-1=左边
@Override
public void onCardVanish(int index, int type) {
Toast.makeText(getContext(), "CardFragment"+ "正在消失=" + index + " 消失type=" + type, Toast.LENGTH_SHORT).show();
}
@Override
public void onItemClick(View cardView, int index) {
Toast.makeText(getContext(), "CardFragment"+"卡片点击=" + index, Toast.LENGTH_SHORT).show();
}
};
slidePanel.setCardSwitchListener(cardSwitchListener);
prepareDataList();
slidePanel.fillData(dataList);
}
//封装数据
private void prepareDataList() {
int num = imagePaths.length;
//重复添加数据10次(测试数据太少)
for (int j = 0; j < 10; j++) {
for (int i = 0; i < num; i++) {
CardDataItem dataItem = new CardDataItem();
dataItem.imagePath = imagePaths[i];
dataList.add(dataItem);
}
}
}
到此主要逻辑代码就编写完了
详细说明代码中已经注释 ,全部代码请看源码
源码:github源码
源码中的TestCardFragment 为使用模板
来源:https://blog.csdn.net/duihuapixiu/article/details/102795767


猜你喜欢
- KMP算法是一种神奇的字符串匹配算法,在对 超长字符串 进行模板匹配的时候比暴力匹配法的效率会高不少。接下来我们从思路入手理解KMP算法。在
- 在使用c#进行控制IIS服务启动停止的时候,提示:【无法打开计算机“.”上的 IISADMIN 服务】这种情况是发生在像vista、win7
- 概述公司的spring boot项目不是使用默认的logback作为日志框架,而是log4j2, 主要原因是logback出现过一个生产问题
- 1. 概述平常我们一般是使用JSON与服务器做数据通信,JSON的话,直接用GSON或者其他库去解析很简单。但是,其他有些服务器会返回XML
- 关键词IDEA 如何控制编辑左侧的功能图标 ICONIDEA 左侧的图标不见了怎么恢复1、操作步骤依次打开 File | Settings
- java多线程的同步方法实例代码先看一个段有关银行存钱的代码:class Bank { private int su
- Wrapper条件构造器updateForSet更新官方文档:https://baomidou.gitee.io/mybatis-plus-
- 什么是RabbitMQ?RabbitMQ是由erlang语言开发的一个基于AMQP(Advanced Message Queuing Pro
- 介绍随着当今处理器中可用的核心数量的增加, 随着对实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行。 Java 提供了自己的多线
- (一) shiro的SecurityManager类结构为:总结: 1.SecurityManager主要作用于登录、登出用创建主题Subj
- 前言泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确
- SpringBoot配置https(SSL证书)最近在做微信小程序,https是必须条件仅需三步SpringBoot2.x版本对比一下这个小
- 先记录下jdk8之前的一些帮助方法判断time是否在now的n天之内/** * 判断time是否在now的n天之内
- 一、注解注解(Annotation): 从jdk5.0开始引进,可以对程序进行解释或被其他程序读取。注解格式:"@注释名"
- 一、问题描述上一次我们使用百度地图实现基本的定位功能,接下来我们继续实现搜索和定位,并使用LocationOverlay绘制定位位置,同时展
- 本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇,
- 说明:基于atguigu学习笔记。简介Webflux是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似
- 效果展示在实际项目当中我们经常看到如下各种剪裁形状的效果,Flutter 为我们提供了非常方便的 Widget 很轻松就可以实现,下面我们来
- 在Springboot中默认的静态资源路径有:classpath:/METAINF/resources/,classpath:/resour
- 1 概念和原理一般的字符串匹配算法都是匹配一个子串,例如KMP、Trie,那么如果同时匹配多个子串呢?此时就需要用到AC自动机了。AC自动机