居中 LAYOUT_MODE_CENTER
顶部 LAYOUT_MODE_TOP
底部 LAYOUT_MODE_BOTTOM
右往左布局 LAYOUT_RIGHT_TO_LEFT
BannerView
package com.example.bannerdemo;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
public class BannerView extends ViewGroup implements Runnable {
private final float SCALE_0 = 0.833333333333f;
private final float SCALE_1 = 0.916666667f;
private final float SCALE_2 = 1f;
private final float ALPHA_0 = 0.4f;
private final float ALPHA_1 = 0.8f;
private final float ALPHA_2 = 1.0f;
private final int DEFAULT_BASELINE = 30;
// index=0 child baseline
private int BASELINE_0 = 0;
// index=1 child baseline
private int BASELINE_1 = 0;
// index=2 child baseline
private int BASELINE_2 = 0;
//动画间隔时间
private int waitTime=1000;
// 只能向左滑动
private final int SCROLL_ORIENTATION_LEFT = 0;
// 只能向右滑动
private final int SCROLL_ORIENTATION_RIGHT = 1;
// 双向滑动
private final int SCROLL_ORIENTATION_ALL = 2;
private final int LAYOUT_MODE_TOP = 0;
private final int LAYOUT_MODE_CENTER = 1;
private final int LAYOUT_MODE_BOTTOM = 2;
private final int LAYOUT_LEFT_TO_RIGHT = 3;
private final int LAYOUT_RIGHT_TO_LEFT = 4;
// 布局模式
int layoutMode = LAYOUT_MODE_CENTER;
// 布局方向
int layoutOrientation = LAYOUT_LEFT_TO_RIGHT;
// 滑动方向
int scrollOrientation = SCROLL_ORIENTATION_LEFT;
// baseLine offset 子view偏移量
int baseLineOffset = DEFAULT_BASELINE;
// 最上面的左边距
int topLeftBaseLine = 0;
//item 点击事件
private OnItemClickListener onItemClickListener;
public BannerView(Context context) {
this(context, null);
}
public BannerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int baseLeftLine = getBaseLeftLine(child);
getBaseTopLine(i);
layoutChild(child, baseLeftLine);
if (i == 2) {
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
onItemClickListener.onItemClick(lp.dataPosition);
}
}
});
} else {
child.setOnClickListener(null);
}
}
}
private int getBaseLeftLine(View child) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int baseLine = 0;
switch (lp.fromIndex) {
case 0:
if (mOffsetPercent > 0) {
baseLine = (int) (BASELINE_0 - mOffsetPercent * BASELINE_1);
if (baseLine < BASELINE_1) {
baseLine = BASELINE_1;
}
lp.scale = (float) (SCALE_0 + ((SCALE_1 - SCALE_0) * mOffsetPercent));
if (lp.scale > SCALE_1) {
lp.scale = SCALE_1;
}
} else {
baseLine = (int) (BASELINE_0 + mOffsetPercent * BASELINE_1);
if (baseLine < BASELINE_1) {
baseLine = BASELINE_1;
}
lp.scale = (float) (SCALE_0 - ((SCALE_1 - SCALE_0) * mOffsetPercent));
if (lp.scale > SCALE_1) {
lp.scale = SCALE_1;
}
}
break;
case 1:
// toIndex=2
if (mOffsetPercent > 0) {
baseLine = (int) (BASELINE_1 - mOffsetPercent * BASELINE_1);
if (baseLine < BASELINE_2) {
baseLine = BASELINE_2;
}
lp.scale = (float) (SCALE_1 + ((SCALE_2 - SCALE_1) * mOffsetPercent));
if (lp.scale > SCALE_2) {
lp.scale = SCALE_2;
}
lp.alpha = (float) (ALPHA_1 + ((ALPHA_2 - ALPHA_1) * mOffsetPercent));
if (lp.alpha > ALPHA_2) {
lp.alpha = ALPHA_2;
}
} else {
baseLine = (int) (BASELINE_1 + mOffsetPercent * BASELINE_1);
if (baseLine < BASELINE_2) {
baseLine = BASELINE_2;
}
lp.scale = (float) (SCALE_1 - ((SCALE_2 - SCALE_1) * mOffsetPercent));
if (lp.scale > SCALE_2) {
lp.scale = SCALE_2;
}
lp.alpha = (float) (ALPHA_1 - ((ALPHA_2 - ALPHA_1) * mOffsetPercent));
if (lp.alpha > ALPHA_2) {
lp.alpha = ALPHA_2;
}
}
break;
case 2:
// toIndex= 0
if (mOffsetPercent > 0) {
baseLine = (int) (BASELINE_2 + mOffsetPercent * BASELINE_0);
if (baseLine >= BASELINE_0) {
baseLine = BASELINE_0;
} else {
baseLine = (int) (BASELINE_2 + mOffsetPercent * child.getWidth());
}
lp.scale = (float) (SCALE_2 - ((SCALE_2 - SCALE_0) * mOffsetPercent));
if (lp.scale <= SCALE_0) {
lp.scale = SCALE_0;
} else {
lp.scale = SCALE_2;
}
lp.alpha = (float) (ALPHA_2 - ((ALPHA_2 - ALPHA_0) * mOffsetPercent));
if (lp.alpha >= ALPHA_0) {
lp.alpha = ALPHA_0;
} else {
lp.alpha = ALPHA_2;
}
} else {
baseLine = (int) (BASELINE_2 + mOffsetPercent * BASELINE_0);
if (baseLine >= BASELINE_0) {
baseLine = BASELINE_0;
} else {
baseLine = (int) (BASELINE_2 + mOffsetPercent * child.getWidth());
}
lp.scale = (float) (SCALE_2 + ((SCALE_2 - SCALE_0) * mOffsetPercent));
if (lp.scale <= SCALE_0) {
lp.scale = SCALE_0;
} else {
lp.scale = SCALE_2;
}
lp.alpha = (float) (ALPHA_2 + ((ALPHA_2 - ALPHA_0) * mOffsetPercent));
if (lp.alpha <= ALPHA_0) {
lp.alpha = ALPHA_0;
} else {
lp.alpha = ALPHA_2;
}
}
break;
}
return baseLine;
}
private int getBaseTopLine(int index) {
int baseLine = 0;
switch (index) {
case 0:
baseLine = 40;
break;
case 1:
baseLine = 20;
break;
case 2:
baseLine = 0;
break;
}
return baseLine;
}
private void layoutChild(View child, int baseLeftLine) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.setAlpha(lp.alpha);
int childWidth = child.getMeasuredWidth();
int childHeight = (int) (child.getMeasuredHeight() * lp.scale);
int top = 0;
int bottom = 0;
if (layoutMode == LAYOUT_MODE_CENTER) {
top = (int) (getHeight() / 2.0f - childHeight / 2.0f);
bottom = (int) (getHeight() / 2.0f + childHeight / 2.0f);
} else if (layoutMode == LAYOUT_MODE_BOTTOM) {
top = getHeight() - childHeight;
bottom = getHeight();
} else if (layoutMode == LAYOUT_MODE_TOP) {
top = 0;
bottom = childHeight;
}
int left = 0;
int right = 0;
if (layoutOrientation == LAYOUT_LEFT_TO_RIGHT) {
left = baseLeftLine;
right = left + childWidth;
} else if (layoutOrientation == LAYOUT_RIGHT_TO_LEFT) {
right = getWidth() - baseLeftLine;
left = right - childWidth;
}
child.layout(left + lp.leftMargin, top + lp.topMargin, right + lp.rightMargin, bottom + lp.bottomMargin);
}
private int measureWidth(int widthMeasureSpec) {
int width = 0;
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
width = size;
} else {
int maxChildWidth = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin);
}
width = maxChildWidth + 60;
}
return width;
}
private int measureHeight(int heightMeasureSpec) {
int height = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
height = size;
} else {
int maxChildHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin);
}
height = maxChildHeight;
}
return height;
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (index > 2) {
return;
}
LayoutParams lp = params instanceof LayoutParams ? (LayoutParams) params : new LayoutParams(params);
lp.fromIndex = index;
switch (index) {
case 0:
lp.scale = SCALE_0;
lp.alpha = ALPHA_0;
BASELINE_0 = baseLineOffset * 2;
break;
case 1:
lp.scale = SCALE_1;
lp.alpha = ALPHA_1;
BASELINE_1 = baseLineOffset;
break;
case 2:
lp.scale = SCALE_2;
lp.alpha = ALPHA_2;
BASELINE_2 = baseLineOffset * 0;
break;
}
Log.e("BannerView", "BASELINE_0 =" + BASELINE_0);
Log.e("BannerView", "BASELINE_1 =" + BASELINE_1);
Log.e("BannerView", "BASELINE_2 =" + BASELINE_2);
super.addView(child, index, params);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public static class LayoutParams extends MarginLayoutParams {
public int fromIndex;
public int toIndex = -1;
public int dataPosition;
public float scale;
public float alpha;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
float mLastX;
boolean isBeingDragged;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float x = ev.getX();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
isBeingDragged = true;
break;
case MotionEvent.ACTION_MOVE:
float offsetX = x - mLastX;
//判断是否触发拖动事件
if (Math.abs(offsetX) > 60) {
//更新上一次的触摸坐标
mLastX = x;
//标记已开始拖拽
}
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
isBeingDragged = false;
break;
case MotionEvent.ACTION_CANCEL:
requestDisallowInterceptTouchEvent(true);
break;
}
return isBeingDragged;
}
float mOffsetX;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (autoAnimator != null && autoAnimator.isRunning()) {
return false;
}
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
removeCallbacks(this);
case MotionEvent.ACTION_MOVE:
float offsetX = x - mLastX;
mOffsetX += offsetX;
if (Math.abs(mOffsetX) > 60) {
isBeingDragged = true;
}
//百分比
mOffsetPercent = mOffsetX / getWidth();
// 开始移动
onChildMove();
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
//开始动画,固定位子
isBeingDragged = false;
run();
break;
}
mLastX = x;
return true;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(event);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (autoAnimation) {
run();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (autoAnimation) {
removeCallbacks(this);
}
if (autoAnimator != null && autoAnimator.isRunning()) {
autoAnimator.cancel();
}
}
@Override
public void run() {
startAutoMove();
}
ValueAnimator autoAnimator;
private boolean autoAnimation = true;
public void setAutoAnimation(boolean autoAnimation) {
this.autoAnimation = autoAnimation;
}
/**
* 自动播放
*/
public void startAutoMove() {
if (getChildCount() != 3) {
Log.e("BannerView", "child need 3 current count is" + getChildCount());
return;
}
if (autoAnimator != null && autoAnimator.isRunning()) {
return;
}
if (isBeingDragged) {
return;
}
autoAnimator = ValueAnimator.ofFloat(mOffsetPercent, -1.f);
autoAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffsetPercent = (float) animation.getAnimatedValue();
onChildMove();
}
});
autoAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
isReordered = false;
mOffsetX = 0;
mOffsetPercent = 0;
if (autoAnimation) {
postDelayed(BannerView.this, waitTime);
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
autoAnimator.setDuration(500);
autoAnimator.setInterpolator(mInterpolator);
autoAnimator.start();
}
/**
* 调整动画的插值器,现在是减速
*/
private Interpolator mInterpolator = new DecelerateInterpolator();
// 滑动的百分比
float mOffsetPercent;
private void onChildMove() {
if (scrollOrientation == SCROLL_ORIENTATION_LEFT) {
if (mOffsetPercent > 0) {
return;
}
}
if (scrollOrientation == SCROLL_ORIENTATION_RIGHT) {
if (mOffsetPercent < 0) {
return;
}
}
changeChildFromAndTop();
updateChildOrder();
requestLayout();
}
private void changeChildFromAndTop() {
if (Math.abs(mOffsetPercent) >= 1) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.fromIndex = lp.toIndex;
lp.toIndex = -1;
}
isReordered = false;
mOffsetPercent %= 1;
mOffsetX %= getWidth();
}
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
switch (lp.fromIndex) {
case 0:
lp.toIndex = mOffsetPercent > 0 ? 1 : 1;
break;
case 1:
lp.toIndex = mOffsetPercent > 0 ? 2 : 2;
break;
case 2:
lp.toIndex = mOffsetPercent > 0 ? 0 : 0;
break;
}
}
}
boolean isReordered;
/**
* 更新子View的层级顺序
*/
private void updateChildOrder() {
Log.e("bannerView", "updateChildOrder mOffsetPercent" + mOffsetPercent);
if (Math.abs(mOffsetPercent) >= .9F) {
if (!isReordered) {
exchangeOrder(2, 0);
exchangeOrder(2, 1);
isReordered = true;
}
} else {
if (isReordered) {
exchangeOrder(0, 2);
isReordered = false;
}
}
for (int i=0;i<3;i++){
View childAt = getChildAt(i);
LayoutParams layoutParams = (LayoutParams) childAt.getLayoutParams();
Log.e("bannerView", "updateChildOrder mOffsetPercent i "+i +"from "+ layoutParams.fromIndex);
Log.e("bannerView", "updateChildOrder mOffsetPercent i "+i +"to " +layoutParams.toIndex);
}
}
/**
* 交换子View的层级顺序
*
* @param fromIndex 原子View索引
* @param toIndex 目标子View索引
*/
private void exchangeOrder(int fromIndex, int toIndex) {
Log.e("bannerView", "exchangeOrder fromIndex" + fromIndex);
Log.e("bannerView", "exchangeOrder toIndex" + toIndex);
if (fromIndex == toIndex || fromIndex >= getChildCount() || toIndex >= getChildCount()) {
return;
}
if (fromIndex > toIndex) {
int temp = fromIndex;
fromIndex = toIndex;
toIndex = temp;
}
View from = getChildAt(fromIndex);
View to = getChildAt(toIndex);
detachViewFromParent(toIndex);
detachViewFromParent(fromIndex);
attachViewToParent(to, fromIndex, to.getLayoutParams());
attachViewToParent(from, toIndex, from.getLayoutParams());
invalidate();
}
/**
* 把目标子View放置到视图层级最底部
*/
private void setAsBottom(View child) {
int index = indexOfChild(child);
if (index > 1) {
return;
}
Log.e("bannerView", "setAsBottom index" + index);
exchangeOrder(index, 0);
}
public interface OnItemClickListener {
void onItemClick(int position);
}
}
参考litepage