Android實(shí)現(xiàn)3D層疊式卡片圖片展示
本文實(shí)例為大家分享了Android實(shí)現(xiàn)3D層疊式卡片圖片展示的具體代碼,供大家參考,具體內(nèi)容如下
先看效果

好了效果看了,感興趣的往下看哦!
整體實(shí)現(xiàn)思路
1、重寫(xiě)RelativeLayout 實(shí)現(xiàn) 鎖定寬高比例的 RelativeLayout
2、自定義一個(gè)支持滑動(dòng)的面板 繼承 ViewGroup
3、卡片View繪制
4、頁(yè)面中使用布局
首先為了更好的展示圖片我們重寫(xiě)一下 RelativeLayout 編寫(xiě)一個(gè)鎖定寬高比例的 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);
//通過(guò)布局獲取寬高比例
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);
// 調(diào)整高度
int width = getMeasuredWidth();
int height = (int) (width * widthHeightRate);
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = height;
setLayoutParams(lp);
}
}
這樣我們就編寫(xiě)好了我們想要的父布局
使用方法
<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:就是設(shè)置寬高的百分比--> <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" /> <!-- 這個(gè)是為了讓我們圖片上有波紋--> </com.petterp.toos.ImageCard.AutoScaleRelativeLayout>
接下來(lái)就是主要布局,也就是展示圖片的布局了
為了實(shí)現(xiàn)滑動(dòng)我們編寫(xiě)一個(gè)支持滑動(dòng)的畫(huà)板
//事件處理
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
// 按下時(shí)保存坐標(biāo)信息
if (action == MotionEvent.ACTION_DOWN) {
this.downPoint.x = (int) ev.getX();
this.downPoint.y = (int) ev.getY();
}
return super.dispatchTouchEvent(ev);
}
/* touch事件的攔截與處理都交給mDraghelper來(lái)處理 */
@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的時(shí)候就對(duì)view重新排序
if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING) {
mDragHelper.abort();
}
orderViewStack();
// 保存初次按下時(shí)arrowFlagView的Y坐標(biāo)
// action_down時(shí)就讓mDragHelper開(kāi)始工作,否則有時(shí)候?qū)е庐惓?
mDragHelper.processTouchEvent(ev);
}
return shouldIntercept && moveFlag;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
try {
// 統(tǒng)一交給mDragHelper處理,由DragHelperCallback實(shí)現(xiàn)拖動(dòng)效果
// 該行代碼可能會(huì)拋異常,正式發(fā)布時(shí)請(qǐng)將這行代碼加上try catch
mDragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
//計(jì)算
@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());
}
// 初始化一些中間參數(shù)
initCenterViewX = viewList.get(0).getLeft();
initCenterViewY = viewList.get(0).getTop();
childWith = viewList.get(0).getMeasuredWidth();
}
//onFinishInflate 當(dāng)View中所有的子控件均被映射成xml后觸發(fā)
@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循環(huán)取view的時(shí)候,是從外層往里取
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);
}
});
}
//裝載數(shù)據(jù)
public void fillData(CardDataItem itemData) {
Glide.with(getContext()).load(itemData.imagePath).into(imageView);
}
/**
* 動(dòng)畫(huà)移動(dòng)到某個(gè)位置
*/
public void animTo(int xPos, int yPos) {
setCurrentSpringPos(getLeft(), getTop());
springX.setEndValue(xPos);
springY.setEndValue(yPos);
}
/**
* 設(shè)置當(dāng)前spring位置
*/
private void setCurrentSpringPos(int xPos, int yPos) {
springX.setCurrentValue(xPos);
springY.setCurrentValue(yPos);
}
接下來(lái)我們需要使用它 編寫(xiě)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="右側(cè)移除"
android:id="@+id/card_left_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button
android:background="#03A9F4"
android:text="右側(cè)移除"
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"+"卡片點(diǎn)擊=" + index, Toast.LENGTH_SHORT).show();
}
};
slidePanel.setCardSwitchListener(cardSwitchListener);
prepareDataList();
slidePanel.fillData(dataList);
}
//封裝數(shù)據(jù)
private void prepareDataList() {
int num = imagePaths.length;
//重復(fù)添加數(shù)據(jù)10次(測(cè)試數(shù)據(jù)太少)
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);
}
}
}
到此主要邏輯代碼就編寫(xiě)完了
詳細(xì)說(shuō)明代碼中已經(jīng)注釋 ,全部代碼請(qǐng)看源碼
源碼:github源碼
源碼中的TestCardFragment 為使用模板
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android進(jìn)階——安卓調(diào)用ESC/POS打印機(jī)打印實(shí)例
本篇文章主要介紹了Android進(jìn)階——安卓調(diào)用ESC/POS打印機(jī)打印實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
android之App Widget開(kāi)發(fā)實(shí)例代碼解析
本篇文章主要介紹了App Widget框架的實(shí)例應(yīng)用,AppWidget就是我們平常在桌面上見(jiàn)到的那種一個(gè)個(gè)的小窗口,利用這個(gè)小窗口可以給用戶(hù)提供一些方便快捷的操作。有需要的可以了解一下。2016-11-11
Android ListView之setEmptyView正確使用方法
這篇文章主要介紹了Android ListView之setEmptyView正確使用方法的相關(guān)資料,希望通過(guò)本文能幫助到大家使用該方法,需要的朋友可以參考下2017-09-09
android源碼探索之定制android關(guān)機(jī)界面的方法
這篇文章主要介紹了android源碼探索之定制android關(guān)機(jī)界面的方法,較為詳細(xì)的分析了Android關(guān)機(jī)界面的相關(guān)原理與代碼實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android自定義實(shí)現(xiàn)圖片加文字功能
這篇文章主要介紹了Android自定義實(shí)現(xiàn)圖片加文字功能的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果
這篇文章主要為大家詳細(xì)介紹了Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android開(kāi)發(fā)實(shí)現(xiàn)拍照功能的方法實(shí)例解析
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)拍照功能的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android拍照功能的具體實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Android 獲取應(yīng)用簽名的實(shí)現(xiàn)
本文主要講下在android中如何獲取應(yīng)用簽名,也方便平時(shí)用來(lái)區(qū)分一個(gè)應(yīng)用是不是原包應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2016-02-02
Android SharedPreferences數(shù)據(jù)存儲(chǔ)詳解
SharedPreferences是安卓平臺(tái)上一個(gè)輕量級(jí)的存儲(chǔ)類(lèi),用來(lái)保存應(yīng)用的一些常用配置,比如Activity狀態(tài),Activity暫停時(shí),將此activity的狀態(tài)保存到SharedPereferences中;當(dāng)Activity重載,系統(tǒng)回調(diào)方法onSaveInstanceState時(shí),再?gòu)腟haredPreferences中將值取出2022-11-11
RecyclerView實(shí)現(xiàn)橫向滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了RecyclerView實(shí)現(xiàn)橫向滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01

