Android實現層疊卡片式banner
阿新 • • 發佈:2020-10-22
本文例項為大家分享了Android實現層疊卡片式banner的具體程式碼,供大家參考,具體內容如下
效果圖如下:
背景
由於公司VIP模組專案需要,本著對ui設計師的尊重,需要實現以上效果圖,在網上找了很多部落格,都不能滿足上面的需求,所以就只能自己硬著頭皮自定義了,下面就是我自定義的view程式碼,做個記錄:
package cn.com.cunw.familydesk.view.vipBanner; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import androidx.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.widget.RelativeLayout; import android.widget.Scroller; import java.util.ArrayList; import cn.com.cunw.familydesk.R; /** * @Description: * @Author: wuJie * @CreateDate: 2020/1/4 10:14 * Copyright (C),2015-2020,*/ public class CoverFlowView extends RelativeLayout { public enum CoverFlowGravity { TOP,BOTTOM,CENTER_VERTICAL } public enum CoverFlowLayoutMode { MATCH_PARENT,WRAP_CONTENT } protected CoverFlowGravity mGravity; protected CoverFlowLayoutMode mLayoutMode; private Scroller mScroller; /** * To store reflections need to remove */ private ArrayList<View> removeViewArray; private SparseArray<View> showViewArray; private int paddingLeft; private int paddingRight; private int paddingTop; private int paddingBottom; private int mWidth; // 控制元件的寬度 private float reflectHeightFraction; private int reflectGap; private int mChildHeight; // child的高度 private int mChildTranslateY; //private int mReflectionTranslateY; private int mVisibleChildCount; // 一屏顯示的圖片數量 protected int VISIBLE_VIEWS = 3; // the visible views left and right 左右兩邊顯示的個數 private ICoverFlowAdapter mAdapter; private float mOffset; //private int mLastOffset; private final int ALPHA_DATUM = 200; // 基礎alphaֵ private int STANDARD_ALPHA; // 基礎縮放值 //private static final float CARD_SCALE = 0.15f; private static float MOVE_POS_MULTIPLE = 3.0f; private static final int TOUCH_MINIMUM_MOVE = 5; private static final float MOVE_SPEED_MULTIPLE = 1; private static final float MAX_SPEED = 6.0f; private static final float FRICTION = 10.0f; private VelocityTracker mVelocity; private int firstIndex = 0; public CoverFlowView(Context context) { super(context); init(); } public CoverFlowView(Context context,AttributeSet attrs) { super(context,attrs); initAttributes(context,attrs); init(); } public CoverFlowView(Context context,AttributeSet attrs,int defStyle) { super(context,attrs,defStyle); initAttributes(context,attrs); init(); } private void initAttributes(Context context,AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ImageCoverFlowView); int totalVisibleChildren = a.getInt( R.styleable.ImageCoverFlowView_visibleImage,3); if (totalVisibleChildren % 2 == 0) { // 一螢幕必須是奇數顯示 throw new IllegalArgumentException("visible image must be an odd number"); } VISIBLE_VIEWS = totalVisibleChildren >> 1; // 計算出左右兩兩邊的顯示個數 reflectHeightFraction = a.getFraction( R.styleable.ImageCoverFlowView_reflectionHeight,100,0.0f); if (reflectHeightFraction > 100) { reflectHeightFraction = 100; } reflectHeightFraction /= 100; reflectGap = a.getDimensionPixelSize( R.styleable.ImageCoverFlowView_reflectionGap,0); mGravity = CoverFlowGravity.values()[a.getInt( R.styleable.ImageCoverFlowView_coverflowGravity,CoverFlowGravity.CENTER_VERTICAL.ordinal())]; mLayoutMode = CoverFlowLayoutMode.values()[a.getInt( R.styleable.ImageCoverFlowView_coverflowLayoutMode,CoverFlowLayoutMode.WRAP_CONTENT.ordinal())]; a.recycle(); } private void init() { removeAllViews(); setWillNotDraw(false); setClickable(true); if (mScroller == null) { mScroller = new Scroller(getContext(),new AccelerateDecelerateInterpolator()); } if (showViewArray == null) { showViewArray = new SparseArray<View>(); } else { showViewArray.clear(); } if (removeViewArray == null) { removeViewArray = new ArrayList<View>(); } else { removeViewArray.clear(); } firstIndex = 0; mChildHeight = 0; mOffset = 0; //mLastOffset = -1; isFirstIn = true; lastMid = 1; isChange = true; // 計算透明度 STANDARD_ALPHA = (255 - ALPHA_DATUM) / VISIBLE_VIEWS; if (mGravity == null) { mGravity = CoverFlowGravity.CENTER_VERTICAL; } if (mLayoutMode == null) { mLayoutMode = CoverFlowLayoutMode.WRAP_CONTENT; } // 一屏 顯示的圖片數量 int visibleCount = (VISIBLE_VIEWS << 1) + 1; for (int i = 0; i < visibleCount && mAdapter != null && i < mAdapter.getCount(); ++i) { View convertView = null; if (removeViewArray.size() > 0) { convertView = removeViewArray.remove(0); } View view = mAdapter.getView(i,convertView,this); showViewArray.put(i,view); addView(view); } requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); if (mAdapter == null || showViewArray.size() <= 0) { return; } paddingLeft = getPaddingLeft(); paddingRight = getPaddingRight(); paddingTop = getPaddingTop(); paddingBottom = getPaddingBottom(); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 一屏顯示的圖片數量 int visibleCount = (VISIBLE_VIEWS << 1) + 1; // 控制元件高度 int availableHeight = heightSize - paddingTop - paddingBottom; int maxChildTotalHeight = 0; for (int i = 0; i < getChildCount() && i < visibleCount && i < showViewArray.size(); ++i) { //View view = showViewArray.get(i+firstIndex); View view = getChildAt(i); measureChild(view,widthMeasureSpec,heightMeasureSpec); //final int childHeight = ScreenUtil.dp2px(getContext(),110); final int childHeight = view.getMeasuredHeight(); final int childTotalHeight = (int) (childHeight + childHeight * reflectHeightFraction + reflectGap); // 孩子的最大高度 maxChildTotalHeight = (maxChildTotalHeight < childTotalHeight) ? childTotalHeight : maxChildTotalHeight; } // 如果控制元件模式為確切值 或者 最大時 if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) { // if height which parent provided is less than child need,scale // child height to parent provide // 如果控制元件高度小於孩子控制元件高度,則縮放孩子高度為控制元件高度 if (availableHeight < maxChildTotalHeight) { mChildHeight = availableHeight; } else { // if larger than,depends on layout mode // if layout mode is match_parent,scale child height to parent // provide // 如果是填充父窗體模式 則將孩子的高度 設為控制元件高度 if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) { mChildHeight = availableHeight; // if layout mode is wrap_content,keep child's original // height // 如果是包裹內容 則將孩子的高度設為孩子允許的最大高度 } else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) { mChildHeight = maxChildTotalHeight; // adjust parent's height // 計算出控制元件的高度 if (heightMode == MeasureSpec.AT_MOST) { heightSize = mChildHeight + paddingTop + paddingBottom; } } } } else { // height mode is unspecified // 如果空間高度 沒有明確定義 // 如果孩子的模式為填充父窗體 if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) { mChildHeight = availableHeight; // 如果孩子的模式為包裹內容 } else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) { mChildHeight = maxChildTotalHeight; // 計算出控制元件的高度 // adjust parent's height heightSize = mChildHeight + paddingTop + paddingBottom; } } // Adjust movement in y-axis according to gravity // 計算出孩子的原點 Y座標 if (mGravity == CoverFlowGravity.CENTER_VERTICAL) {// 豎直居中 mChildTranslateY = (heightSize >> 1) - (mChildHeight >> 1); } else if (mGravity == CoverFlowGravity.TOP) {// 頂部對齊 mChildTranslateY = paddingTop; } else if (mGravity == CoverFlowGravity.BOTTOM) {// 底部對齊 mChildTranslateY = heightSize - paddingBottom - mChildHeight; } //mReflectionTranslateY = (int) (mChildTranslateY + mChildHeight - mChildHeight // * reflectHeightFraction); setMeasuredDimension(widthSize,heightSize); mVisibleChildCount = visibleCount; mWidth = widthSize; } boolean isFirstIn = true; // 第一次初始化該控制元件 int lastMid = 1; boolean isChange = true; @Override protected void onLayout(boolean changed,int l,int t,int r,int b) { if (mAdapter == null || mAdapter.getCount() <= 0 || showViewArray.size() <= 0) { return; } final float offset = mOffset; int i = 0; int mid = (int) Math.floor(offset + 0.5); //右邊孩子的數量 int rightChild = (mVisibleChildCount % 2 == 0) ? (mVisibleChildCount >> 1) - 1 : mVisibleChildCount >> 1; //左邊孩子的數量 int leftChild = mVisibleChildCount >> 1; if (!isFirstIn) { if (lastMid + 1 == mid) { int actuallyPositionStart = getActuallyPosition(lastMid - leftChild); View view = showViewArray.get(actuallyPositionStart); showViewArray.remove(actuallyPositionStart); removeViewArray.add(view); removeView(view); View convertView = null; if (removeViewArray.size() > 0) { convertView = removeViewArray.remove(0); } int actuallyPositionEnd = getActuallyPosition(mid + rightChild); //TODO View viewItem = mAdapter.getView(actuallyPositionEnd,this); showViewArray.put(actuallyPositionEnd,viewItem); addView(viewItem); isChange = true; } else if (lastMid - 1 == mid) { int actuallyPositionEnd = getActuallyPosition(lastMid + rightChild); View view = showViewArray.get(actuallyPositionEnd); showViewArray.remove(actuallyPositionEnd); removeViewArray.add(view); removeView(view); View convertView = null; if (removeViewArray.size() > 0) { convertView = removeViewArray.remove(0); } int actuallyPositionStart = getActuallyPosition(mid - leftChild); //TODO View viewItem = mAdapter.getView(actuallyPositionStart,this); showViewArray.put(actuallyPositionStart,viewItem); addView(viewItem,0); isChange = true; } } else { isFirstIn = false; } lastMid = mid; // draw the left children // 計算左邊孩子的位置 int startPos = mid - leftChild; for (i = startPos; i < mid; ++i) { //TODO 計算左邊孩子的位置 View child = layoutLeftChild(i,i - offset); if (isChange) { int actuallyPosition = getActuallyPosition(i); mAdapter.getData(child,actuallyPosition); } } // 計算 右邊 和 中間 int endPos = mid + rightChild; int j = -VISIBLE_VIEWS; for (i = endPos; i >= mid; --i,j = j + 2) { //TODO 計算右邊和中間孩子的位置 View child = layoutRightChild(i + j,actuallyPosition); } } isChange = false; } private View layoutLeftChild(int position,float offset) { //獲取實際的position int actuallyPosition = getActuallyPosition(position); View child = showViewArray.get(actuallyPosition); if (child != null) { makeChildTransfromer(child,actuallyPosition,offset); } return child; } private View layoutRightChild(int position,float offset) { //獲取實際的position int actuallyPosition = getActuallyPosition(position); View child = showViewArray.get(actuallyPosition); if (child != null) { makeChildTransfromer(child,offset); } return child; } /** * 對 bitmap 進行偽 3d 變換 * * @param child * @param position * @param offset */ private void makeChildTransfromer(View child,int position,float offset) { //child.layout(0,ScreenUtil.dp2px(getContext(),200),110)); child.layout(0,child.getMeasuredWidth(),child.getMeasuredHeight()); float scale = 0; scale = 1 - Math.abs(offset) * 0.25f; // 延x軸移動的距離應該根據center圖片決定 float translateX = 0; final int originalChildHeight = (int) (mChildHeight - mChildHeight * reflectHeightFraction - reflectGap); //final int childTotalHeight = (int) (child.getHeight() // + child.getHeight() * reflectHeightFraction + reflectGap); final float originalChildHeightScale = (float) originalChildHeight / child.getHeight(); final float childHeightScale = originalChildHeightScale * scale; final int childWidth = (int) (child.getWidth() * childHeightScale); final int centerChildWidth = (int) (child.getWidth() * originalChildHeightScale); //final int centerChildWidth = (int) (child.getWidth() * childHeightScale); int leftSpace = ((mWidth >> 1) - paddingLeft) - (centerChildWidth >> 1); int rightSpace = (((mWidth >> 1) - paddingRight) - (centerChildWidth >> 1)); //計算出水平方向的x座標 if (offset <= 0) translateX = ((float) leftSpace / VISIBLE_VIEWS) * (VISIBLE_VIEWS + offset) + paddingLeft; else translateX = mWidth - ((float) rightSpace / VISIBLE_VIEWS) * (VISIBLE_VIEWS - offset) - childWidth - paddingRight; //根據offset 算出透明度 float alpha = 254 - Math.abs(offset) * STANDARD_ALPHA; child.setAlpha(0); if (alpha < 0) { alpha = 0; } else if (alpha > 254) { alpha = 254; } child.setAlpha(alpha / 254.0f); float adjustedChildTranslateY = 0; ObjectAnimator anim1 = ObjectAnimator.ofFloat(child,"scaleX",1.0f,childHeightScale); ObjectAnimator anim2 = ObjectAnimator.ofFloat(child,"scaleY",childHeightScale); AnimatorSet animSet = new AnimatorSet(); animSet.setDuration(0); //兩個動畫同時執行 animSet.playTogether(anim1,anim2); child.setPivotX(0); child.setPivotY(child.getHeight() / 2); //顯示的呼叫invalidate child.invalidate(); animSet.setTarget(child); animSet.start(); child.setTranslationX(translateX); child.setTranslationY(mChildTranslateY + adjustedChildTranslateY); } /** * 獲取頂部Item position * * @return */ public int getTopViewPosition() { return getActuallyPosition(lastMid); } /** * 獲取頂部Item View * * @return */ public View getTopView() { return showViewArray.get(getActuallyPosition(VISIBLE_VIEWS + lastMid)); } /** * Convert draw-index to index in adapter * * @param position position to draw * @return */ private int getActuallyPosition(int position) { if (mAdapter!=null){ int max = mAdapter.getCount(); position += VISIBLE_VIEWS; while (position < 0 || position >= max) { if (position < 0) { position += max; } else if (position >= max) { position -= max; } } } return position; } public void setAdapter(@NonNull ICoverFlowAdapter adapter) { mAdapter = adapter; init(); } public ICoverFlowAdapter getAdapter() { return mAdapter; } private boolean onTouchMove = false; //是否正在執行觸控移動邏輯 private View touchViewItem = null; private boolean isOnTopView = false; @Override public boolean onTouchEvent(MotionEvent event) { if (getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } if (isOnAnimator) { return false; } int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: if (mAdapter==null){ return false; } onTouchMove = true; if (mScroller.computeScrollOffset()) { mScroller.abortAnimation(); invalidate(); requestLayout(); } touchBegan(event); touchViewItem = getTopView(); isOnTopView = inRangeOfView(touchViewItem,event); if (isOnTopView) { sendLongClickAction(); } return true; case MotionEvent.ACTION_MOVE: touchMoved(event); removeLongClickAction(); touchViewItem = null; isOnTopView = false; return true; case MotionEvent.ACTION_UP: removeLongClickAction(); if (isOnTopView && touchViewItem == getTopView() && inRangeOfView(touchViewItem,event)) { if (mTopViewClickListener != null) { mTopViewClickListener.onClick(getTopViewPosition(),getTopView()); } } touchViewItem = null; isOnTopView = false; touchEnded(event); return true; } return false; } private Runnable longClickRunnable = null; /** * 傳送長點選事件 Runnable */ private void sendLongClickAction() { removeLongClickAction(); longClickRunnable = new Runnable() { @Override public void run() { touchViewItem = null; isOnTopView = false; if (mTopViewLongClickListener != null) { mTopViewLongClickListener.onLongClick(getTopViewPosition(),getTopView()); } } }; postDelayed(longClickRunnable,600); } /** * 移除長點選事件 Runnable */ private void removeLongClickAction() { if (longClickRunnable != null) { removeCallbacks(longClickRunnable); longClickRunnable = null; } } private boolean inRangeOfView(View view,MotionEvent ev) { Rect frame = new Rect(); view.getHitRect(frame); if (frame.contains((int) ev.getX(),(int) ev.getY())) { return true; } return false; } private OnTopViewClickListener mTopViewClickListener; private OnTopViewLongClickListener mTopViewLongClickListener; private OnTopViewChangeListener mTopViewChangeListener; /** * 設定 TopView 的點選監聽 * * @param topViewClickListener */ public void setOnTopViewClickListener(OnTopViewClickListener topViewClickListener) { this.mTopViewClickListener = topViewClickListener; } /** * 獲取 TopView 的點選監聽 * * @return */ public OnTopViewClickListener getOnTopViewClickListener() { return this.mTopViewClickListener; } /** * 設定 TopView 的長點選監聽 * * @param topViewLongClickListener */ public void setOnTopViewLongClickListener(OnTopViewLongClickListener topViewLongClickListener) { this.mTopViewLongClickListener = topViewLongClickListener; } /** * 設定 TopView 的長點選監聽 * * @param mTopViewChangeListener */ public void setOnTopViewChangeListener(OnTopViewChangeListener mTopViewChangeListener) { this.mTopViewChangeListener = mTopViewChangeListener; } /** * 獲取 TopView 的長點選監聽 * * @return */ public OnTopViewLongClickListener getOnTopViewLongClickListener() { return this.mTopViewLongClickListener; } public interface OnTopViewClickListener { void onClick(int position,View itemView); } public interface OnTopViewLongClickListener { void onLongClick(int position,View itemView); } public interface OnTopViewChangeListener { void onItemChange(int position); } private boolean mTouchMoved; private float mTouchStartPos; private float mTouchStartX; private float mTouchStartY; private long mStartTime; private float mStartOffset; private void touchBegan(MotionEvent event) { endAnimation(); float x = event.getX(); mTouchStartX = x; mTouchStartY = event.getY(); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartOffset = mOffset; mTouchMoved = false; mTouchStartPos = (x / mWidth) * MOVE_POS_MULTIPLE - 5; mTouchStartPos /= 2; mVelocity = VelocityTracker.obtain(); mVelocity.addMovement(event); } private Runnable mAnimationRunnable; private void endAnimation() { if (mAnimationRunnable != null) { mOffset = (float) Math.floor(mOffset + 0.5); invalidate(); requestLayout(); removeCallbacks(mAnimationRunnable); mAnimationRunnable = null; } } private void touchMoved(MotionEvent event) { float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5; pos /= 2; if (!mTouchMoved) { float dx = Math.abs(event.getX() - mTouchStartX); float dy = Math.abs(event.getY() - mTouchStartY); if (dx < TOUCH_MINIMUM_MOVE && dy < TOUCH_MINIMUM_MOVE) return; mTouchMoved = true; } mOffset = mStartOffset + mTouchStartPos - pos; invalidate(); requestLayout(); mVelocity.addMovement(event); } private void touchEnded(MotionEvent event) { float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5; pos /= 2; if (mTouchMoved || (mOffset - Math.floor(mOffset)) != 0) { mStartOffset += mTouchStartPos - pos; mOffset = mStartOffset; mVelocity.addMovement(event); mVelocity.computeCurrentVelocity(1000); float speed = mVelocity.getXVelocity(); speed = (speed / mWidth) * MOVE_SPEED_MULTIPLE; if (speed > MAX_SPEED) speed = MAX_SPEED; else if (speed < -MAX_SPEED) speed = -MAX_SPEED; startAnimation(-speed); if (mTopViewChangeListener != null) { mTopViewChangeListener.onItemChange(getTopViewPosition()); } } else { Log.e("xyw"," touch ==>" + event.getX() + "," + event.getY()); onTouchMove = false; } mVelocity.clear(); mVelocity.recycle(); } private float mStartSpeed; private float mDuration; private void startAnimation(float speed) { if (mAnimationRunnable != null) { onTouchMove = false; return; } float delta = speed * speed / (FRICTION * 2); if (speed < 0) delta = -delta; float nearest = mStartOffset + delta; nearest = (float) Math.floor(nearest + 0.5f); mStartSpeed = (float) Math.sqrt(Math.abs(nearest - mStartOffset) * FRICTION * 2); if (nearest < mStartOffset) mStartSpeed = -mStartSpeed; mDuration = Math.abs(mStartSpeed / FRICTION); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mAnimationRunnable = new Runnable() { @Override public void run() { driveAnimation(); } }; post(mAnimationRunnable); } private void driveAnimation() { float elapsed = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) / 1000.0f; if (elapsed >= mDuration) { endAnimation(); onTouchMove = false; } else { updateAnimationAtElapsed(elapsed); post(mAnimationRunnable); } } private void updateAnimationAtElapsed(float elapsed) { if (elapsed > mDuration) elapsed = mDuration; float delta = Math.abs(mStartSpeed) * elapsed - FRICTION * elapsed * elapsed / 2; if (mStartSpeed < 0) delta = -delta; mOffset = mStartOffset + delta; invalidate(); requestLayout(); } @Override public void computeScroll() { super.computeScroll(); // 算出移動到某一個虛擬點時 mOffset 的值,然後 invalidate 重繪 if (mScroller.computeScrollOffset()) { final int currX = mScroller.getCurrX(); mOffset = (float) currX / 100; invalidate(); } } private boolean isOnAnimator = false; //是否正在進行點選切換動畫 /** * 翻到前頁 */ public void gotoPrevious() { doAnimator(-1.0f); } /** * 前進到後一頁 */ public void gotoForward() { doAnimator(1.0f); } public void doAnimator(float target) { if (isOnAnimator || onTouchMove) { //如果正在執行點選切換動畫 或者 正在執行觸控移動 return; } isOnAnimator = true; ValueAnimator animator = ValueAnimator.ofFloat(mOffset,mOffset + target); animator.setDuration(300).start(); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mOffset = (Float) animation.getAnimatedValue(); invalidate(); requestLayout(); } }); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { isOnAnimator = false; } }); } // @Override // public boolean dispatchTouchEvent(MotionEvent ev) { // if (ev.getAction() == MotionEvent.ACTION_MOVE) { // return true; // 禁止滑動 // } // return super.dispatchTouchEvent(ev); // } }
相關回調介面類
public interface ICoverFlowAdapter { int getCount(); Object getItem(int position); long getItemId(int position); View getView(int position,View convertView,ViewGroup parent); void getData(View view,int position); }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。