Android实现层叠卡片式banner
Banboofly 人气:0效果图如下:
背景
由于公司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, 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, convertView, 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, convertView, 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, i - offset); if (isChange) { int actuallyPosition = getActuallyPosition(i); mAdapter.getData(child, 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, actuallyPosition, offset); } return child; } /** * 对 bitmap 进行伪 3d 变换 * * @param child * @param position * @param offset */ private void makeChildTransfromer(View child, int position, float offset) { //child.layout(0, 0, ScreenUtil.dp2px(getContext(), 200),ScreenUtil.dp2px(getContext(), 110)); child.layout(0, 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", 1.0f, 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); }
加载全部内容