Skip to content

Instantly share code, notes, and snippets.

@log2c
Last active December 15, 2022 03:07
Show Gist options
  • Save log2c/28c3a0889219c78cc156f78e6395cbda to your computer and use it in GitHub Desktop.
Save log2c/28c3a0889219c78cc156f78e6395cbda to your computer and use it in GitHub Desktop.
Android TV列表焦点选中效果 '''mMetroViewBorderImpl.attachTo(recyclerView)'''
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class AutoLayoutManager extends GridLayoutManager {
public AutoLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public AutoLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public AutoLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
@Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler,
RecyclerView.State state) {
// Need to be called in order to layout new row/column
View nextFocus = super.onFocusSearchFailed(focused, focusDirection, recycler, state);
if (nextFocus == null) {
return null;
}
int fromPos = getPosition(focused);
int nextPos = getNextViewPos(fromPos, focusDirection);
return findViewByPosition(nextPos);
}
/**
* Manually detect next view to focus.
*
* @param fromPos from what position start to seek.
* @param direction in what direction start to seek. Your regular
* {@code View.FOCUS_*}.
* @return adapter position of next view to focus. May be equal to
* {@code fromPos}.
*/
protected int getNextViewPos(int fromPos, int direction) {
int offset = calcOffsetToNextView(direction);
if (hitBorder(fromPos, offset)) {
//return fromPos;
}
return fromPos + offset;
}
/**
* Calculates position offset.
*
* @param direction regular {@code View.FOCUS_*}.
* @return position offset according to {@code direction}.
*/
protected int calcOffsetToNextView(int direction) {
int spanCount = getSpanCount();
int orientation = getOrientation();
if (orientation == VERTICAL) {
switch (direction) {
case View.FOCUS_DOWN:
return spanCount;
case View.FOCUS_UP:
return -spanCount;
case View.FOCUS_RIGHT:
return 1;
case View.FOCUS_LEFT:
return -1;
}
} else if (orientation == HORIZONTAL) {
switch (direction) {
case View.FOCUS_DOWN:
return 1;
case View.FOCUS_UP:
return -1;
case View.FOCUS_RIGHT:
return spanCount;
case View.FOCUS_LEFT:
return -spanCount;
}
}
return 0;
}
/**
* Checks if we hit borders.
*
* @param from from what position.
* @param offset offset to new position.
* @return {@code true} if we hit border.
*/
private boolean hitBorder(int from, int offset) {
int spanCount = getSpanCount();
if (Math.abs(offset) == 1) {
int spanIndex = from % spanCount;
int newSpanIndex = spanIndex + offset;
return newSpanIndex < 0 || newSpanIndex >= spanCount;
} else {
int newPos = from + offset;
return newPos < 0 || newPos >= spanCount;
}
}
}
import android.view.View;
public interface IMetroViewBorder {
void onFocusChanged(View target, View oldFocus, View newFocus);
void onScrollChanged(View target, View attachView);
void onLayout(View target, View attachView);
void onTouchModeChanged(View target, View attachView, boolean isInTouchMode);
void onAttach(View target, View attachView);
void OnDetach(View target, View view);
}
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AdapterView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"unchecked", "unused"})
public class MetroViewBorderHandler implements IMetroViewBorder {
private static final String TAG = MetroViewBorderHandler.class.getSimpleName();
protected boolean mScalable = true;//是否缩小
protected float mScale = 1.1f;//设置放大比例
protected long mDurationTranslate = 200;//焦点移动的动画时间
protected int mMargin = 0;
protected View lastFocus, oldLastFocus;//上一个焦点,新的位置的焦点
protected AnimatorSet mAnimatorSet;//动画集合,可组合多个动画
protected List<Animator> mAnimatorList = new ArrayList<>();
protected View mTarget;
protected boolean mEnableTouch = true;
public MetroViewBorderHandler() {
mFocusListener.add(mFocusMoveListener);//设置焦点移动时监听
mFocusListener.add(mFocusScaleListener);//设置焦点放大缩小监听
mFocusListener.add(mFocusPlayListener);
mFocusListener.add(mAbsListViewFocusListener);
}
public interface FocusListener {
void onFocusChanged(View oldFocus, View newFocus);
}
protected List<FocusListener> mFocusListener = new ArrayList<>(1);
protected List<Animator.AnimatorListener> mAnimatorListener = new ArrayList<>(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(mDurationTranslate);//设置动画时间
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 = 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<>(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.contains(oldFocus) && attacheViews.contains(newFocus)) {
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 = 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<>(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(mDurationTranslate);
mAnimatorSet.playTogether(animatorList);
mAnimatorSet.start();
oldOn.oldFocus = null;
oldOn.newFocus = null;
newOn.oldFocus = null;
newOn.newFocus = b.getSelectedView();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
protected List<Animator> getScaleAnimator(View view, boolean isScale) {
List<Animator> animatorList = new ArrayList<>(2);
if (!mScalable) return animatorList;//如果没有放大,直接返回
try {
float scaleBefore = 1.0f;//放大前比例
float scaleAfter = mScale;//放大后比例
if (!isScale) {//
scaleBefore = mScale;
scaleAfter = 1.0f;
}
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", scaleBefore, scaleAfter);
ObjectAnimator scaleY = 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<>();
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 valuesWidthHolder = 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, valuesWidthHolder, 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 static 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.contains(oldFocus) && attacheViews.contains(newFocus)) {
return scope;
}
if (oldFocus != null && newFocus != null) {
if (oldFocus.getParent() != newFocus.getParent()) {
if ((!attacheViews.contains(newFocus.getParent())) || (!attacheViews.contains(oldFocus.getParent()) && 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.contains(oldFocus.getParent())) {
scope.oldFocus = null;
}
} else {
if (!attacheViews.contains(newFocus.getParent())) {
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.contains(newFocus)) {
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;
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(mDurationTranslate);
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<>(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(mDurationTranslate);
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;
}
}
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@SuppressWarnings({"unchecked", "unused"})
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 && viewGroup != null) {
ViewTreeObserver viewTreeObserver = viewGroup.getViewTreeObserver();
if (viewTreeObserver != null && 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();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment