原文:
TV Metro界面(仿泰捷视频TV版)源码解析
如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易
版权声明:我已委托“维权骑士”(rightknights.com)为我的文章进行维权行动.转载务必转载所有,且须注明出处。否则保留追究法律责任 https://blog.csdn.net/hejjunlin/article/details/52822499
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52822499
前言:上一篇介绍了仿泰捷视频TV版的效果,对应github:https://github.com/hejunlin2013/TVSample,今天就介绍下对应的源码部分
先看下View的层级结构图:
在SmoothHorizonalScrollView(继承HorizonalScrollView)下挂上DrawingOrderRelativeLayout(继承自RelativeLayout),接下来就是一个个的MetroItem,每个Item下包含ImageView(海报图),CornerView(VIP角标),TextView(海报图简介),像第一张和第二张,以及最后两张,显示是频道入口,只有一个ImageView,无CornerView及TextView。
角标View
public class CornerVew extends View {
private String mTextContent;//显示字体内容
private int mTextColor;//字体颜色
private float mTextSize;//字体大小
private boolean mTextBold;//是否字体加粗
private boolean mFillTriangle;//是否三角形
private boolean mTextAllCaps;//
private int mBackgroundColor;//背景颜色
private float mMinSize;//最小值
private float mPadding;//padding值
private int mGravity;//
private static final int DEFAULT_DEGREES = 45;//切角
private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path mPath = new Path();
public CornerVew(Context context) {
this(context, null);
}
public CornerVew(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAttributes(context, attrs);
mTextPaint.setTextAlign(Paint.Align.CENTER);//从中间开始画
}
private void obtainAttributes(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CornerVew);
mTextContent = ta.getString(R.styleable.CornerVew_cv_text);
mTextColor = ta.getColor(R.styleable.CornerVew_cv_text_color, Color.parseColor("#ffffff"));
mTextSize = ta.getDimension(R.styleable.CornerVew_cv_text_size, sp2px(11));
mTextBold = ta.getBoolean(R.styleable.CornerVew_cv_text_bold, true);//加粗
mTextAllCaps = ta.getBoolean(R.styleable.CornerVew_cv_text_all_caps, true);
mFillTriangle = ta.getBoolean(R.styleable.CornerVew_cv_fill_triangle, false);
mBackgroundColor = ta.getColor(R.styleable.CornerVew_cv_background_color, Color.parseColor("#FF4081"));
mMinSize = ta.getDimension(R.styleable.CornerVew_cv_min_size, mFillTriangle ? dp2px(35) : dp2px(50));
mPadding = ta.getDimension(R.styleable.CornerVew_cv_padding, dp2px(3.5f));
mGravity = ta.getInt(R.styleable.CornerVew_cv_gravity, Gravity.TOP | Gravity.LEFT);
ta.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
int size = getHeight();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setFakeBoldText(mTextBold);
mBackgroundPaint.setColor(mBackgroundColor);
float textHeight = mTextPaint.descent() - mTextPaint.ascent();
if (mFillTriangle) {
if (mGravity == (Gravity.TOP | Gravity.LEFT)) {//左上角
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, true);
} else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {//右上角
mPath.reset();
mPath.moveTo(size, 0);
mPath.lineTo(0, 0);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, DEFAULT_DEGREES, canvas, true);
} else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {//左下角
mPath.reset();
mPath.moveTo(0, size);
mPath.lineTo(0, 0);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, DEFAULT_DEGREES, canvas, false);
} else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {//右下角
mPath.reset();
mPath.moveTo(size, size);
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, false);
}
} else {
double delta = (textHeight + mPadding * 2) * Math.sqrt(2);
if (mGravity == (Gravity.TOP | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, (float) (size - delta));
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.lineTo((float) (size - delta), 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, -DEFAULT_DEGREES, canvas, textHeight, true);
} else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo((float) delta, 0);
mPath.lineTo(size, (float) (size - delta));
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, DEFAULT_DEGREES, canvas, textHeight, true);
} else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, (float) delta);
mPath.lineTo((float) (size - delta), size);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, DEFAULT_DEGREES, canvas, textHeight, false);
} else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(0, size);
mPath.lineTo((float) delta, size);
mPath.lineTo(size, (float) delta);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, -DEFAULT_DEGREES, canvas, textHeight, false);
}
}
}
private void drawText(int size, float degrees, Canvas canvas, float textHeight, boolean isTop) {
canvas.save();
canvas.rotate(degrees, size / 2f, size / 2f);
float delta = isTop ? -(textHeight + mPadding * 2) / 2 : (textHeight + mPadding * 2) / 2;
float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
canvas.restore();
}
private void drawTextWhenFill(int size, float degrees, Canvas canvas, boolean isTop) {
canvas.save();
canvas.rotate(degrees, size / 2f, size / 2f);
float delta = isTop ? -size / 4 : size / 4;
float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = measureWidth(widthMeasureSpec);
setMeasuredDimension(measuredWidth, measuredWidth);
}
private int measureWidth(int widthMeasureSpec) {
int result;
int specMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量模式
int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取测量大小
if (specMode == MeasureSpec.EXACTLY) {//精确模式
result = specSize;
} else {
int padding = getPaddingLeft() + getPaddingRight();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
float textWidth = mTextPaint.measureText(mTextContent + "");
result = (int) ((padding + (int) textWidth) * Math.sqrt(2));
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
result = Math.max((int) mMinSize, result);
}
return result;
}
protected int dp2px(float dp) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
protected int sp2px(float sp) {
final float scale = getResources().getDisplayMetrics().scaledDensity;
return (int) (sp * scale + 0.5f);
}
}
绘制子视图顺序的Layout
public class DrawingOrderRelativeLayout extends RelativeLayout {
private int position = 0;
public DrawingOrderRelativeLayout(Context context) {
super(context);
}
public DrawingOrderRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.setChildrenDrawingOrderEnabled(true);
}
public void setCurrentPosition(int pos) {
this.position = pos;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
View focused = findFocus();//找到焦点
int pos = indexOfChild(focused);
if (pos >= 0 && pos < getChildCount()) {
setCurrentPosition(pos);
postInvalidate();
}
return super.dispatchKeyEvent(event);
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {//改变ViewGroup的子视图绘制顺序
View v = getFocusedChild();
int pos = indexOfChild(v);
if (pos >= 0 && pos < childCount)
setCurrentPosition(pos);
if (i == childCount - 1) {//从最后一个view开始绘制
return position;
}
if (i == position) {
return childCount - 1;
}
return i;
}
}
MetroItem
public class MetroItemFrameLayout extends FrameLayout implements IMetroItemRound {
private MetroItemRound mMetroItemRound;
public MetroItemFrameLayout(Context context) {
super(context);
init(context, null, 0);
}
public MetroItemFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public MetroItemFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
mMetroItemRound = new MetroItemRound(this, context, attrs, defStyle);
setWillNotDraw(false);//设置为false时,就会调用自定义的布局
}
@Override
public void draw(Canvas canvas) {
mMetroItemRound.draw(canvas);
}
@Override
public MetroItemRound getRoundImpl() {
return mMetroItemRound;
}
@Override
public void drawRadious(Canvas canvas) {
super.draw(canvas);
}
}
MetroItem的圆角
public class MetroItemRound {
private float mRadius;//圆角
private float mTopLeftRadius;//左上角的圆角
private float mTopRightRadius;//右上角的圆角
private float mBottomLeftRadius;//左下角的圆角
private float mBottomRightRadius;//右下角的圆角
private Paint mPaint;//画笔
private Path mPath;//画布
private IMetroItemRound mView;
public MetroItemRound(IMetroItemRound view, Context context, AttributeSet attrs, int defStyle) {
init(context, attrs, defStyle);
mView = view;
}
private void init(Context context, AttributeSet attrs, int defStyle) {
mPaint = new Paint();//实例化画笔
mPaint.setColor(Color.WHITE);//设置画笔颜色为白色
mPaint.setAntiAlias(true);//设置抗锯齿
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));//取下层绘制非交集部分
mPath = new Path();//设置路径
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MetroItemRound, defStyle, 0);
mBottomLeftRadius = a.getDimension(R.styleable.MetroItemRound_bottomLeftRadius, -1);
mBottomRightRadius = a.getDimension(R.styleable.MetroItemRound_bottomRightRadius, -1);
mTopLeftRadius = a.getDimension(R.styleable.MetroItemRound_topLeftRadius, -1);
mTopRightRadius = a.getDimension(R.styleable.MetroItemRound_topRightRadius, -1);
mRadius = a.getDimension(R.styleable.MetroItemRound_radius, -1);
if (mRadius > 0) {
if (mBottomLeftRadius < 0)
mBottomLeftRadius = mRadius;
if (mBottomRightRadius < 0)
mBottomRightRadius = mRadius;
if (mTopLeftRadius < 0)
mTopLeftRadius = mRadius;
if (mTopRightRadius < 0)
mTopRightRadius = mRadius;
}
a.recycle();
}
public void draw(Canvas canvas) {
if (mBottomLeftRadius <= 0 && mBottomRightRadius <= 0 && mTopRightRadius <= 0 && mTopLeftRadius <= 0) {
mView.drawRadious(canvas);
return;
}
int width = mView.getWidth();
int height = mView.getHeight();
int count = canvas.save();
int count2 = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
addRoundPath(width, height);
mView.drawRadious(canvas);//画一个圆角
canvas.drawPath(mPath, mPaint);
canvas.restoreToCount(count2);
canvas.restoreToCount(count);
}
private void addRoundPath(int width, int height) {
//topleft path
if (mTopLeftRadius > 0) {
Path topLeftPath = new Path();
topLeftPath.moveTo(0, mTopLeftRadius);//移动画笔到最左边x为0,y为mTopLeftRadius
topLeftPath.lineTo(0, 0);//绘制直线,从坐标0,0开始
topLeftPath.lineTo(mTopLeftRadius, 0);//
RectF arc = new RectF(0, 0, mTopLeftRadius * 2, mTopLeftRadius * 2);//矩形区域
//和Rect的区别是RectF表示精确到浮点型
topLeftPath.arcTo(arc, -90, -90);//画弧线的路径,从-90到-90,逆时针画
topLeftPath.close();//
mPath.addPath(topLeftPath);
}
//topRight path
if (mTopRightRadius > 0) {
Path topRightPath = new Path();
topRightPath.moveTo(width, mTopRightRadius);//移动画笔到最右边x为width,y为mTopRightRadius
topRightPath.lineTo(width, 0);
topRightPath.lineTo(width - mTopRightRadius, 0);
RectF arc = new RectF(width - mTopRightRadius * 2, 0, width, mTopRightRadius * 2);
topRightPath.arcTo(arc, -90, 90);//这里是画180的椭圆
topRightPath.close();
mPath.addPath(topRightPath);
}
//bottomLeft path
if (mBottomLeftRadius > 0) {
Path bottomLeftPath = new Path();
bottomLeftPath.moveTo(0, height - mBottomLeftRadius);
bottomLeftPath.lineTo(0, height);
bottomLeftPath.lineTo(mBottomLeftRadius, height);
RectF arc = new RectF(0, height - mBottomLeftRadius * 2, mBottomLeftRadius * 2, height);
bottomLeftPath.arcTo(arc, 90, 90);
bottomLeftPath.close();
mPath.addPath(bottomLeftPath);
}
//bottomRight path
if (mBottomRightRadius > 0) {
Path bottomRightPath = new Path();
bottomRightPath.moveTo(width - mBottomRightRadius, height);
bottomRightPath.lineTo(width, height);
bottomRightPath.lineTo(width, height - mBottomRightRadius);
RectF arc = new RectF(width - mBottomRightRadius * 2, height - mBottomRightRadius * 2, width, height);
bottomRightPath.arcTo(arc, 0, 90);
bottomRightPath.close();
mPath.addPath(bottomRightPath);
}
}
}
MetroItem中外边框处理类
public class MetroViewBorderHandler implements IMetroViewBorder {
private String TAG = MetroViewBorderHandler.class.getSimpleName();
protected boolean mScalable = true;//是否缩小
protected float mScale = 1.1f;//设置放大比例
protected long mDurationTraslate = 200;//焦点移动的动画时间
protected int mMargin = 0;
protected View lastFocus, oldLastFocus;//上一个焦点,新的位置的焦点
protected AnimatorSet mAnimatorSet;//动画集合,可组合多个动画
protected List<Animator> mAnimatorList = new ArrayList<Animator>();
protected View mTarget;
protected boolean mEnableTouch = true;
public MetroViewBorderHandler() {
mFocusListener.add(mFocusMoveListener);//设置焦点移动时监听
mFocusListener.add(mFocusScaleListener);//设置焦点放大缩小监听
mFocusListener.add(mFocusPlayListener);
mFocusListener.add(mAbsListViewFocusListener);//用作listview,gridview相关
}
public interface FocusListener {
void onFocusChanged(View oldFocus, View newFocus);
}
protected List<FocusListener> mFocusListener = new ArrayList<FocusListener>(1);
protected List<Animator.AnimatorListener> mAnimatorListener = new ArrayList<Animator.AnimatorListener>(1);
public FocusListener mFocusScaleListener = new FocusListener() {
@Override
public void onFocusChanged(View oldFocus, View newFocus) {
//焦点变化,设置新焦点移动的位置变成放大状态
mAnimatorList.addAll(getScaleAnimator(newFocus, true));
if (oldFocus != null) {
//上一个view(之前是焦点态,onFocusChanged变成非焦点态)不为null,设置上一个为缩小状态
mAnimatorList.addAll(getScaleAnimator(oldFocus, false));
}
}
};
public FocusListener mFocusPlayListener = new FocusListener() {
@Override
public void onFocusChanged(View oldFocus, View newFocus) {
try {
if (newFocus instanceof AbsListView) {//如果新的view是AbsListView的实例,直接return
return;
}
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setInterpolator(new DecelerateInterpolator(1));//设置插值器
animatorSet.setDuration(mDurationTraslate);//设置动画时间
animatorSet.playTogether(mAnimatorList);//表示两个动画同进执行
for (Animator.AnimatorListener listener : mAnimatorListener) {
animatorSet.addListener(listener);
}
mAnimatorSet = animatorSet;
if (oldFocus == null) {//之前view为null,表示首次状态时
animatorSet.setDuration(0);//无动画时长
mTarget.setVisibility(View.VISIBLE);
}
animatorSet.start();//开启动画
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
public FocusListener mFocusMoveListener = new FocusListener() {
@Override
public void onFocusChanged(View oldFocus, View newFocus) {
if (newFocus == null) return;//下一个view不存在时,直接return
try {
mAnimatorList.addAll(getMoveAnimator(newFocus, 0, 0));//添加
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
public FocusListener mAbsListViewFocusListener = new FocusListener() {
@Override
public void onFocusChanged(View oldFocus, View newFocus) {
try {
if (oldFocus == null) {
for (int i = 0; i < attacheViews.size(); i++) {
View view = attacheViews.get(i);
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
mTarget.setVisibility(View.INVISIBLE);
if (mFirstFocus) {
absListView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
try {
absListView.removeOnLayoutChangeListener(this);
int factorX = 0, factorY = 0;
Rect rect = new Rect();
View firstView = (View) absListView.getSelectedView();
firstView.getLocalVisibleRect(rect);
if (Math.abs(rect.left - rect.right) > firstView.getMeasuredWidth()) {
factorX = (Math.abs(rect.left - rect.right) - firstView.getMeasuredWidth()) / 2 - 1;
factorY = (Math.abs(rect.top - rect.bottom) - firstView.getMeasuredHeight()) / 2;
}
List<Animator> animatorList = new ArrayList<Animator>(3);
animatorList.addAll(getScaleAnimator(firstView, true));
animatorList.addAll(getMoveAnimator(firstView, factorX, factorY));
mTarget.setVisibility(View.VISIBLE);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(0);
animatorSet.playTogether(animatorList);
animatorSet.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
}
break;
}
}
} else if (oldFocus instanceof AbsListView && newFocus instanceof AbsListView) {
if (attacheViews.indexOf(oldFocus) >= 0 && attacheViews.indexOf(newFocus) >= 0) {
AbsListView a = (AbsListView) oldFocus;
AbsListView b = (AbsListView) newFocus;
MyOnItemSelectedListener oldOn = (MyOnItemSelectedListener) onItemSelectedListenerList.get(oldFocus);
MyOnItemSelectedListener newOn = (MyOnItemSelectedListener) onItemSelectedListenerList.get(newFocus);
int factorX = 0, factorY = 0;
Rect rect = new Rect();
View firstView = (View) b.getSelectedView();
firstView.getLocalVisibleRect(rect);
if (Math.abs(rect.left - rect.right) > firstView.getMeasuredWidth()) {
factorX = (Math.abs(rect.left - rect.right) - firstView.getMeasuredWidth()) / 2 - 1;
factorY = (Math.abs(rect.top - rect.bottom) - firstView.getMeasuredHeight()) / 2;
}
List<Animator> animatorList = new ArrayList<Animator>(3);
animatorList.addAll(getScaleAnimator(firstView, true));
animatorList.addAll(getScaleAnimator(a.getSelectedView(), false));
animatorList.addAll(getMoveAnimator(firstView, factorX, factorY));
mTarget.setVisibility(View.VISIBLE);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.setDuration(mDurationTraslate);
mAnimatorSet.playTogether(animatorList);
mAnimatorSet.start();
oldOn.oldFocus = null;
oldOn.newFocus = null;
newOn.oldFocus = null;
if (newOn.newFocus != null && newOn.oldFocus != null) {
newOn.newFocus = null;
} else {
newOn.newFocus = b.getSelectedView();
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
protected List<Animator> getScaleAnimator(View view, boolean isScale) {
List<Animator> animatorList = new ArrayList<Animator>(2);
if (!mScalable) return animatorList;//如果没有放大,直接返回
try {
float scaleBefore = 1.0f;//放大前比例
float scaleAfter = mScale;//放大后比例
if (!isScale) {//
scaleBefore = mScale;
scaleAfter = 1.0f;
}
ObjectAnimator scaleX = new ObjectAnimator().ofFloat(view, "scaleX", scaleBefore, scaleAfter);
ObjectAnimator scaleY = new ObjectAnimator().ofFloat(view, "scaleY", scaleBefore, scaleAfter);
animatorList.add(scaleX);
animatorList.add(scaleY);
} catch (Exception ex) {
ex.printStackTrace();
}
return animatorList;
}
protected List<Animator> getMoveAnimator(View newFocus, int factorX, int factorY) {
List<Animator> animatorList = new ArrayList<Animator>();
int newXY[];
int oldXY[];
try {
newXY = getLocation(newFocus);//新的
oldXY = getLocation(mTarget);
int newWidth;
int newHeight;
int oldWidth = mTarget.getMeasuredWidth();
int oldHeight = mTarget.getMeasuredHeight();
if (mScalable) {
float scaleWidth = newFocus.getMeasuredWidth() * mScale;
float scaleHeight = newFocus.getMeasuredHeight() * mScale;
newWidth = (int) (scaleWidth + mMargin * 2 + 0.5);
newHeight = (int) (scaleHeight + mMargin * 2 + 0.5);
newXY[0] = (int) (newXY[0] - (newWidth - newFocus.getMeasuredWidth()) / 2.0f) + factorX;
newXY[1] = (int) (newXY[1] - (newHeight - newFocus.getMeasuredHeight()) / 2.0f + 0.5 + factorY);
} else {
newWidth = newFocus.getWidth();
newHeight = newFocus.getHeight();
}
if (oldHeight == 0 && oldWidth == 0) {
oldHeight = newHeight;
oldWidth = newWidth;
}
PropertyValuesHolder valuesWithdHolder = PropertyValuesHolder.ofInt("width", oldWidth, newWidth);
PropertyValuesHolder valuesHeightHolder = PropertyValuesHolder.ofInt("height", oldHeight, newHeight);
PropertyValuesHolder valuesXHolder = PropertyValuesHolder.ofFloat("translationX", oldXY[0], newXY[0]);
PropertyValuesHolder valuesYHolder = PropertyValuesHolder.ofFloat("translationY", oldXY[1], newXY[1]);
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mTarget, valuesWithdHolder, valuesHeightHolder, valuesYHolder, valuesXHolder);
scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public synchronized void onAnimationUpdate(ValueAnimator animation) {
int width = (int) animation.getAnimatedValue("width");
int height = (int) animation.getAnimatedValue("height");
float translationX = (float) animation.getAnimatedValue("translationX");
float translationY = (float) animation.getAnimatedValue("translationY");
View view = (View) scaleAnimator.getTarget();
assert view != null;
int w = view.getLayoutParams().width;
view.getLayoutParams().width = width;
view.getLayoutParams().height = height;
if (width > 0) {
view.requestLayout();
view.postInvalidate();
}
}
});
animatorList.add(scaleAnimator);
} catch (Exception ex) {
ex.printStackTrace();
}
return animatorList;
}
protected int[] getLocation(View view) {
int[] location = new int[2];//location[0]代表x坐标,location [1] 代表y坐标。
try {
//获取在整个屏幕内的绝对坐标,注意这个值是要从屏幕顶端算起,也就是包括了通知栏的高度。
view.getLocationOnScreen(location);
} catch (Exception ex) {
ex.printStackTrace();
}
return location;
}
public void addOnFocusChanged(FocusListener focusListener) {
this.mFocusListener.add(focusListener);
}
public void removeOnFocusChanged(FocusListener focusListener) {
this.mFocusListener.remove(focusListener);
}
public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
this.mAnimatorListener.add(animatorListener);
}
public void removeAnimatorListener(Animator.AnimatorListener animatorListener) {
this.mAnimatorListener.remove(animatorListener);
}
private class VisibleScope {
public boolean isVisible;
public View oldFocus;
public View newFocus;
}
protected VisibleScope checkVisibleScope(View oldFocus, View newFocus) {
VisibleScope scope = new VisibleScope();
try {
scope.oldFocus = oldFocus;
scope.newFocus = newFocus;
scope.isVisible = true;
if (attacheViews.indexOf(oldFocus) >= 0 && attacheViews.indexOf(newFocus) >= 0) {
return scope;
}
if (oldFocus != null && newFocus != null) {
if (oldFocus.getParent() != newFocus.getParent()) {
if ((attacheViews.indexOf(newFocus.getParent()) < 0) || (attacheViews.indexOf(oldFocus.getParent()) < 0 && attacheViews.indexOf(newFocus.getParent()) > 0)) {
mTarget.setVisibility(View.INVISIBLE);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(getScaleAnimator(oldFocus, false));
animatorSet.setDuration(0).start();
scope.isVisible = false;
return scope;
} else {
mTarget.setVisibility(View.VISIBLE);
}
if (attacheViews.indexOf(oldFocus.getParent()) < 0) {
scope.oldFocus = null;
}
} else {
if (attacheViews.indexOf(newFocus.getParent()) < 0) {
mTarget.setVisibility(View.INVISIBLE);
scope.isVisible = false;
return scope;
}
}
}
mTarget.setVisibility(View.VISIBLE);
} catch (Exception ex) {
ex.printStackTrace();
}
return scope;
}
@Override
public void onFocusChanged(View target, View oldFocus, View newFocus) {
try {
Log.d(TAG, "onFocusChanged:" + oldFocus + "=" + newFocus);
if (newFocus == null && attacheViews.indexOf(newFocus) >= 0) {
return;
}
if (oldFocus == newFocus)
return;
if (mAnimatorSet != null && mAnimatorSet.isRunning()) {//如果动画正在运行时
mAnimatorSet.end();
}
lastFocus = newFocus;
oldLastFocus = oldFocus;
mTarget = target;
VisibleScope scope = checkVisibleScope(oldFocus, newFocus);
if (!scope.isVisible) {
return;
} else {
oldFocus = scope.oldFocus;
newFocus = scope.newFocus;
oldLastFocus = scope.oldFocus;
}
if (isScrolling || newFocus == null || newFocus.getWidth() <= 0 || newFocus.getHeight() <= 0)
return;
mAnimatorList.clear();//清除动画
for (FocusListener f : this.mFocusListener) {
f.onFocusChanged(oldFocus, newFocus);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void onScrollChanged(View target, View attachView) {
try {
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void onLayout(View target, View attachView) {
try {
ViewGroup viewGroup = (ViewGroup) attachView.getRootView();
if (target.getParent() != null && target.getParent() != viewGroup) {
target.setVisibility(View.GONE);
if (mFirstFocus)
viewGroup.requestFocus();//如果是首次获取焦点,强制变成焦点态
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
protected boolean mFirstFocus = true;
public void setFirstFocus(boolean b) {
this.mFirstFocus = b;
}
@Override
public void onTouchModeChanged(View target, View attachView, boolean isInTouchMode) {
try {
if (mEnableTouch && isInTouchMode) {
target.setVisibility(View.INVISIBLE);
if (lastFocus != null) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(getScaleAnimator(lastFocus, false));
animatorSet.setDuration(0).start();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
protected boolean isScrolling = false;
protected List<View> attacheViews = new ArrayList<>();
protected Map<View, AdapterView.OnItemSelectedListener> onItemSelectedListenerList = new HashMap<>();
@Override
public void onAttach(View target, View attachView) {
try {
mTarget = target;
if (target.getParent() != null && (target.getParent() instanceof ViewGroup)) {
ViewGroup vg = (ViewGroup) target.getParent();
vg.removeView(target);
}
ViewGroup vg = (ViewGroup) attachView.getRootView();
vg.addView(target);
target.setVisibility(View.GONE);
if (attachView instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) attachView;
RecyclerView.OnScrollListener recyclerViewOnScrollListener = null;
recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
try {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
isScrolling = false;
View oldFocus = oldLastFocus;
View newFocus = lastFocus;
VisibleScope scope = checkVisibleScope(oldFocus, newFocus);
if (!scope.isVisible) {
return;
} else {
oldFocus = scope.oldFocus;
newFocus = scope.newFocus;
}
AnimatorSet animatorSet = new AnimatorSet();
List<Animator> list = new ArrayList<>();
list.addAll(getScaleAnimator(newFocus, true));
list.addAll(getMoveAnimator(newFocus, 0, 0));
animatorSet.setDuration(mDurationTraslate);
animatorSet.playTogether(list);
animatorSet.start();
} else if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
isScrolling = true;
if (lastFocus != null) {
List<Animator> list = getScaleAnimator(lastFocus, false);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(150);
animatorSet.playTogether(list);
animatorSet.start();
}
}
} catch (Exception ex) {
}
}
};
recyclerView.addOnScrollListener(recyclerViewOnScrollListener);
} else if (attachView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) attachView;
final AdapterView.OnItemSelectedListener onItemSelectedListener = absListView.getOnItemSelectedListener();
View temp = null;
if (absListView.getChildCount() > 0) {
temp = absListView.getChildAt(0);
}
final View tempFocus = temp;
MyOnItemSelectedListener myOnItemSelectedListener = new MyOnItemSelectedListener();
myOnItemSelectedListener.onItemSelectedListener = onItemSelectedListener;
myOnItemSelectedListener.oldFocus = temp;
absListView.setOnItemSelectedListener(myOnItemSelectedListener);
onItemSelectedListenerList.put(attachView, myOnItemSelectedListener);
}
attacheViews.add(attachView);
} catch (Exception ex) {
ex.printStackTrace();
}
}
protected class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
public View oldFocus = null;
public View newFocus = null;
public AnimatorSet animatorSet;
public AdapterView.OnItemSelectedListener onItemSelectedListener;
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
try {
if (onItemSelectedListener != null && parent != null) {
onItemSelectedListener.onItemSelected(parent, view, position, id);
}
if (newFocus == null)
return;
newFocus = view;
Rect rect = new Rect();
view.getLocalVisibleRect(rect);
ViewGroup vg = (ViewGroup) newFocus.getParent();
int factorX = 0, factorY = 0;
if (Math.abs(rect.left - rect.right) > newFocus.getMeasuredWidth()) {
factorX = (Math.abs(rect.left - rect.right) - newFocus.getMeasuredWidth()) / 2 - 1;
factorY = (Math.abs(rect.top - rect.bottom) - newFocus.getMeasuredHeight()) / 2;
}
List<Animator> animatorList = new ArrayList<Animator>(3);
animatorList.addAll(getScaleAnimator(newFocus, true));
if (oldFocus != null)
animatorList.addAll(getScaleAnimator(oldFocus, false));
animatorList.addAll(getMoveAnimator(newFocus, factorX, factorY));
mTarget.setVisibility(View.VISIBLE);
if (animatorSet != null && animatorSet.isRunning())
animatorSet.end();
animatorSet = new AnimatorSet();
animatorSet.setDuration(mDurationTraslate);
animatorSet.playTogether(animatorList);
animatorSet.start();
oldFocus = newFocus;
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
if (onItemSelectedListener != null) {
onItemSelectedListener.onNothingSelected(parent);
}
}
}
@Override
public void OnDetach(View targe, View view) {
if (targe.getParent() == view) {
((ViewGroup) view).removeView(targe);
}
attacheViews.remove(view);
}
public void setEnableTouch(boolean enableTouch) {
this.mEnableTouch = enableTouch;
}
public boolean isScalable() {
return mScalable;
}
public void setScalable(boolean scalable) {
this.mScalable = scalable;
}
public float getScale() {
return mScale;
}
public void setScale(float scale) {
this.mScale = scale;
}
public int getMargin() {
return mMargin;
}
public void setMargin(int mMargin) {
this.mMargin = mMargin;
}
}
MetroViewBorder用于对焦点变化处理及滚动处理
public class MetroViewBorderImpl<X extends View> implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnTouchModeChangeListener {
private static final String TAG = MetroViewBorderImpl.class.getSimpleName();
private ViewGroup mViewGroup;
private IMetroViewBorder mMetroViewBorder;
private X mView;
private View mLastView;
public MetroViewBorderImpl(Context context) {
this(context, null, 0);
}
public MetroViewBorderImpl(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MetroViewBorderImpl(Context context, AttributeSet attrs, int defStyleAttr) {
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mMetroViewBorder = new MetroViewBorderHandler();
mView = (X) new View(context, attrs, defStyleAttr);
}
public MetroViewBorderImpl(X view) {
this.mView = view;
mMetroViewBorder = new MetroViewBorderHandler();
}
public MetroViewBorderImpl(X view, IMetroViewBorder border) {
this.mView = view;
mMetroViewBorder = border;
}
public MetroViewBorderImpl(Context context, int resId) {
this((X) LayoutInflater.from(context).inflate(resId, null, false));
}
public X getView() {
return mView;
}
public void setBackgroundResource(int resId) {
if (mView != null)
mView.setBackgroundResource(resId);
}
@Override
public void onScrollChanged() {
mMetroViewBorder.onScrollChanged(mView, mViewGroup);
}
@Override
public void onGlobalLayout() {
mMetroViewBorder.onLayout(mView, mViewGroup);
}
@Override
public void onTouchModeChanged(boolean isInTouchMode) {
mMetroViewBorder.onTouchModeChanged(mView, mViewGroup, isInTouchMode);
}
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {// 4.3
if (oldFocus == null && mLastView != null) {
oldFocus = mLastView;
}
}
if (mMetroViewBorder != null)
mMetroViewBorder.onFocusChanged(mView, oldFocus, newFocus);
mLastView = newFocus;
} catch (Exception ex) {
ex.printStackTrace();
}
}
public <T extends MetroViewBorderHandler> T getViewBorder() {
return (T) mMetroViewBorder;
}
public void setBorder(IMetroViewBorder border) {
this.mMetroViewBorder = border;
}
public void attachTo(ViewGroup viewGroup) {
try {
if (viewGroup == null) {
if (mView.getContext() instanceof Activity) {
Activity activity = (Activity) mView.getContext();
viewGroup = (ViewGroup) activity.getWindow().getDecorView().getRootView();//获取顶层view
}
}
if (mViewGroup != viewGroup) {
ViewTreeObserver viewTreeObserver = viewGroup.getViewTreeObserver();
if (viewTreeObserver.isAlive() && mViewGroup == null) {
viewTreeObserver.addOnGlobalFocusChangeListener(this);
viewTreeObserver.addOnScrollChangedListener(this);
viewTreeObserver.addOnGlobalLayoutListener(this);
viewTreeObserver.addOnTouchModeChangeListener(this);
}
mViewGroup = viewGroup;
}
mMetroViewBorder.onAttach(mView, viewGroup);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void detach() {
detachFrom(mViewGroup);
}
public void detachFrom(ViewGroup viewGroup) {
try {
if (viewGroup == mViewGroup) {
ViewTreeObserver viewTreeObserver = mViewGroup.getViewTreeObserver();//获取view树的观察者
viewTreeObserver.removeOnGlobalFocusChangeListener(this);//通知全局性移除相应的listener
viewTreeObserver.removeOnScrollChangedListener(this);
viewTreeObserver.removeOnGlobalLayoutListener(this);
viewTreeObserver.removeOnTouchModeChangeListener(this);
mMetroViewBorder.OnDetach(mView, viewGroup);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。
如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易