/*
* Copyright (C) 2014 Lucas Rocha
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.owen.tvrecyclerview.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.View;
import com.owen.tvrecyclerview.BaseLayoutManager;
import com.owen.tvrecyclerview.R;
import com.owen.tvrecyclerview.TwoWayLayoutManager;
import java.lang.reflect.Constructor;
public class TvRecyclerView extends RecyclerView {
private static final String LOGTAG = TvRecyclerView.class.getSimpleName() + ":::";
private static final int DEFAULT_SELECTED_ITEM_OFFSET = 40;
private static final int DEFAULT_LOAD_MORE_BEFOREHAND_COUNT = 4;
private int mVerticalSpacingWithMargins = 0;
private int mHorizontalSpacingWithMargins = 0;
private int mSelectedItemOffsetStart;
private int mSelectedItemOffsetEnd;
private boolean mSelectedItemCentered;
private boolean mIsBaseLayoutManager;
private boolean mIsInterceptKeyEvent;
private boolean mIsSelectFirstVisiblePosition;
private boolean mIsMenu;
private boolean mHasFocus = false;
private int mLoadMoreBeforehandCount;
private int mPreSelectedPosition = 0;
private int mSelectedPosition = 0;
private int mOverscrollValue;
private int mOffset = -1;
private OnItemListener mOnItemListener;
private OnInBorderKeyEventListener mOnInBorderKeyEventListener;
private OnLoadMoreListener mOnLoadMoreListener;
private boolean mHasMore = true;
private boolean mLoadingMore = false;
private ItemListener mItemListener;
private static final Class<?>[] sConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
private final Object[] sConstructorArgs = new Object[2];
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 110:
mHasFocus = true;
onFocusChanged(mHasFocus, View.FOCUS_DOWN, null);
break;
case 111:
if(getFocusedChild() == null) {
mHasFocus = false;
onFocusChanged(mHasFocus, View.FOCUS_DOWN, null);
}
break;
}
}
};
public TvRecyclerView(Context context) {
this(context, null);
}
public TvRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView, defStyle, 0);
final String name = a.getString(R.styleable.TvRecyclerView_tv_layoutManager);
if (!TextUtils.isEmpty(name)) {
loadLayoutManagerFromName(context, attrs, name);
}
mSelectedItemCentered = a.getBoolean(R.styleable.TvRecyclerView_tv_selectedItemIsCentered, false);
mIsInterceptKeyEvent = a.getBoolean(R.styleable.TvRecyclerView_tv_isInterceptKeyEvent, false);
mIsMenu = a.getBoolean(R.styleable.TvRecyclerView_tv_isMenu, false);
mIsSelectFirstVisiblePosition = a.getBoolean(R.styleable.TvRecyclerView_tv_isSelectFirstVisiblePosition, false);
mLoadMoreBeforehandCount = a.getInt(R.styleable.TvRecyclerView_tv_loadMoreBeforehandCount, DEFAULT_LOAD_MORE_BEFOREHAND_COUNT);
mSelectedItemOffsetStart = a.getDimensionPixelOffset(R.styleable.TvRecyclerView_tv_selectedItemOffsetStart, DEFAULT_SELECTED_ITEM_OFFSET);
mSelectedItemOffsetEnd = a.getDimensionPixelOffset(R.styleable.TvRecyclerView_tv_selectedItemOffsetEnd, DEFAULT_SELECTED_ITEM_OFFSET);
a.recycle();
}
private void init(Context context){
setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
setChildrenDrawingOrderEnabled(true);
setWillNotDraw(true); // 自身不作onDraw处理
setHasFixedSize(true);
setOverScrollMode(View.OVER_SCROLL_NEVER);
setClipChildren(false);
setClipToPadding(false);
setClickable(false);
setFocusable(true);
setFocusableInTouchMode(true);
mItemListener = new ItemListener() {
/**
* 子控件的点击事件
* @param itemView
*/
@Override
public void onClick(View itemView) {
if(null != mOnItemListener) {
mOnItemListener.onItemClick(TvRecyclerView.this, itemView, getChildLayoutPosition(itemView));
}
}
/**
* 子控件的焦点变动事件
* @param itemView
* @param hasFocus
*/
@Override
public void onFocusChange(final View itemView, boolean hasFocus) {
mHandler.removeMessages(110);
mHandler.removeMessages(111);
if(hasFocus && !mHasFocus){
mHandler.sendEmptyMessage(110);
}
else if(!hasFocus && mHasFocus) {
mHandler.sendEmptyMessageDelayed(111, 10);
}
if(null != itemView) {
final int position = getChildLayoutPosition(itemView);
itemView.setSelected(hasFocus);
if (hasFocus) {
mSelectedPosition = position;
if(mIsMenu && itemView.isActivated()) {
itemView.setActivated(false);
}
if(null != mOnItemListener)
mOnItemListener.onItemSelected(TvRecyclerView.this, itemView, position);
} else {
mPreSelectedPosition = position;
if(mIsMenu) {
// 解决选中后无状态表达的问题,selector中使用activated代表选中后焦点移走
itemView.postDelayed(new Runnable() {
@Override
public void run() {
if(!hasFocus()) {
itemView.setActivated(true);
}
}
}, 3);
}
if(null != mOnItemListener)
mOnItemListener.onItemPreSelected(TvRecyclerView.this, itemView, position);
}
}
}
};
}
private void loadLayoutManagerFromName(Context context, AttributeSet attrs, String name) {
try {
final int dotIndex = name.indexOf('.');
if (dotIndex == -1) {
name = "com.owen.tvrecyclerview.widget." + name;
} else if (dotIndex == 0) {
final String packageName = context.getPackageName();
name = packageName + "." + name;
}
Class<? extends TwoWayLayoutManager> clazz =
context.getClassLoader().loadClass(name).asSubclass(TwoWayLayoutManager.class);
Constructor<? extends TwoWayLayoutManager> constructor =
clazz.getConstructor(sConstructorSignature);
sConstructorArgs[0] = context;
sConstructorArgs[1] = attrs;
setLayoutManager(constructor.newInstance(sConstructorArgs));
} catch (Exception e) {
throw new IllegalStateException("Could not load TwoWayLayoutManager from " +
"class: " + name, e);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// Log.i(LOGTAG, "onLayout: ");
// Log.i("@@@@", "onLayout...mHasFocus="+mHasFocus + "hasFocus()="+hasFocus());
if(!hasFocus()) {
setItemActivated(mPreSelectedPosition);
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
// Log.i(LOGTAG, "onMeasure: ");
}
@Override
public void setLayoutManager(LayoutManager layout) {
mIsBaseLayoutManager = layout instanceof BaseLayoutManager;
super.setLayoutManager(layout);
}
@Override
public void setAdapter(final Adapter adapter) {
if(null == adapter) return;
//修复重新setAdapter后第一条被遮挡的问题
View view = getChildAt(0);
if(null != view && null != getAdapter()) {
int start = isVertical() ? getLayoutManager().getDecoratedTop(view) : getLayoutManager().getDecoratedLeft(view);
start -= isVertical() ? getPaddingTop() : getPaddingLeft();
scrollBy(start, start);
}
super.setAdapter(adapter);
mPreSelectedPosition = 0;
//解决删除数据焦点丢失问题
adapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
// Log.i("@@@@", "onItemRangeRemoved...mHasFocus="+mHasFocus + "hasFocus()="+hasFocus());
if(mHasFocus) {
requestFocus();
postDelayed(new Runnable() {
@Override
public void run() {
requestFocus();
}
}, 300);
}
}
});
}
public void requestDefaultFocus() {
if(mIsMenu || !mIsSelectFirstVisiblePosition) {
setSelection(mPreSelectedPosition);
} else {
setSelection(getFirstVisiblePosition());
}
}
public void setSelection(int position) {
if(null == getAdapter() || position < 0 || position >= getAdapter().getItemCount()) {
return;
}
if(getDescendantFocusability() != FOCUS_BEFORE_DESCENDANTS) {
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
}
View view = getChildAt(position - getFirstVisiblePosition());
if(null != view) {
view.requestFocus();
}
else {
LinearSmoothScroller scroller = new LinearSmoothScroller(getContext()) {
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
if (getChildCount() == 0) {
return null;
}
final int direction = targetPosition < getFirstVisiblePosition() ? -1 : 1;
if (isVertical()) {
return new PointF(0, direction);
} else {
return new PointF(direction, 0);
}
}
@Override
protected void onStop() {
super.onStop();
final View itemView = findViewByPosition(getTargetPosition());
if (null != itemView) {
itemView.post(new Runnable() {
@Override
public void run() {
itemView.requestFocus();
}
});
}
}
};
scroller.setTargetPosition(position);
getLayoutManager().startSmoothScroll(scroller);
}
}
public int getSelectedPosition() {
return mSelectedPosition;
}
public int getPreSelectedPosition() {
return mPreSelectedPosition;
}
public void setSelectFirstVisiblePosition(boolean selectFirstVisiblePosition) {
mIsSelectFirstVisiblePosition = selectFirstVisiblePosition;
}
public boolean isSelectFirstVisiblePosition() {
return mIsSelectFirstVisiblePosition;
}
public void setMenu(boolean menu) {
mIsMenu = menu;
}
public boolean isMenu() {
return mIsMenu;
}
public void setLoadMoreBeforehandCount(int loadMoreBeforehandCount) {
mLoadMoreBeforehandCount = loadMoreBeforehandCount;
}
public int getLoadMoreBeforehandCount() {
return mLoadMoreBeforehandCount;
}
public boolean isHasMore() {
return mHasMore;
}
public void setHasMore(boolean hasMore) {
mHasMore = hasMore;
}
/**
* 设置选中的Item距离开始或结束的偏移量;
* 与滚动方向有关;
* 与setSelectedItemAtCentered()方法二选一
* @param offsetStart
* @param offsetEnd
*/
public void setSelectedItemOffset(int offsetStart, int offsetEnd) {
this.mSelectedItemOffsetStart = offsetStart;
this.mSelectedItemOffsetEnd = offsetEnd;
}
/**
* 设置选中的Item居中;
* 与setSelectedItemOffset()方法二选一
* @param isCentered
*/
public void setSelectedItemAtCentered(boolean isCentered) {
this.mSelectedItemCentered = isCentered;
}
public boolean isSelectedItemCentered() {
return mSelectedItemCentered;
}
public void setLoadingMore(boolean loadingMore) {
mLoadingMore = loadingMore;
}
public boolean isLoadingMore() {
return mLoadingMore;
}
/**
* 设置是否拦截OnKey事件
* @param interceptKeyEvent
*/
public void setInterceptKeyEvent(boolean interceptKeyEvent) {
mIsInterceptKeyEvent = interceptKeyEvent;
}
public boolean isInterceptKeyEvent() {
return mIsInterceptKeyEvent;
}
public boolean isVertical() {
if(mIsBaseLayoutManager) {
BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
return layout.isVertical();
} else if (getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();
return layout.getOrientation() == LinearLayoutManager.VERTICAL;
}
return true;
}
private int getFreeSize() {
if(!isVertical()) {
return getFreeHeight();
} else {
return getFreeWidth();
}
}
private int getFreeHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
private int getFreeWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
@Override
public void requestChildFocus(View child, View focused) {
// Log.i(LOGTAG, "requestChildFocus: "+child);
if(null != child) {
if (mSelectedItemCentered) {
mSelectedItemOffsetStart = !isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight());
mSelectedItemOffsetStart /= 2;
mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
}
}
super.requestChildFocus(child, focused);
}
@Override
public void onScrolled(int dx, int dy) {
if(isVertical()) {
mOverscrollValue = dy;
} else {
mOverscrollValue = dx;
}
super.onScrolled(dx, dy);
}
@Override
public void onScrollStateChanged(int state) {
if(state == SCROLL_STATE_IDLE) {
mOffset = -1;
if (Math.abs(mOverscrollValue) != 1) {
mOverscrollValue = 1;
final View focuse = getFocusedChild();
if (null != mOnItemListener && null != focuse) {
mOnItemListener.onReviseFocusFollow(this, focuse, getChildLayoutPosition(focuse));
}
}
// 加载更多回调
if(null != mOnLoadMoreListener && !mLoadingMore && mHasMore) {
if(getLastVisiblePosition() >= getAdapter().getItemCount() - (1 + mLoadMoreBeforehandCount)) {
mHasMore = mOnLoadMoreListener.onLoadMore();
}
}
}
super.onScrollStateChanged(state);
}
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
final int parentLeft = getPaddingLeft();
final int parentTop = getPaddingTop();
final int parentRight = getWidth() - getPaddingRight();
final int parentBottom = getHeight() - getPaddingBottom();
final int childLeft = child.getLeft() + rect.left;
final int childTop = child.getTop() + rect.top;
final int childRight = childLeft + rect.width();
final int childBottom = childTop + rect.height();
final int offScreenLeft = Math.min(0, childLeft - parentLeft - mSelectedItemOffsetStart);
final int offScreenTop = Math.min(0, childTop - parentTop - mSelectedItemOffsetStart);
final int offScreenRight = Math.max(0, childRight - parentRight + mSelectedItemOffsetEnd);
final int offScreenBottom = Math.max(0, childBottom - parentBottom + mSelectedItemOffsetEnd);
final boolean canScrollHorizontal = getLayoutManager().canScrollHorizontally();
final boolean canScrollVertical = getLayoutManager().canScrollVertically();
// Favor the "start" layout direction over the end when bringing one side or the other
// of a large rect into view. If we decide to bring in end because start is already
// visible, limit the scroll such that start won't go out of bounds.
final int dx;
if(canScrollHorizontal) {
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
dx = offScreenRight != 0 ? offScreenRight
: Math.max(offScreenLeft, childRight - parentRight);
} else {
dx = offScreenLeft != 0 ? offScreenLeft
: Math.min(childLeft - parentLeft, offScreenRight);
}
} else {
dx = 0;
}
// Favor bringing the top into view over the bottom. If top is already visible and
// we should scroll to make bottom visible, make sure top does not go out of bounds.
final int dy;
if(canScrollVertical) {
dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);
} else {
dy = 0;
}
if(cannotScrollForwardOrBackward(isVertical() ? dy : dx)) {
mOffset = -1;
} else {
mOffset = isVertical() ? dy : dx;
if (dx != 0 || dy != 0) {
if (immediate) {
scrollBy(dx, dy);
} else {
smoothScrollBy(dx, dy);
}
return true;
}
}
// 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法
postInvalidate();
return false;
}
/**
* 获取选中ITEM的滚动偏移量
* @return
*/
public int getSelectedItemScrollOffset() {
return mOffset;
}
/**
* 判断当前是否还可以向前或后滚动
* @param value
* @return
*/
private boolean cannotScrollForwardOrBackward(int value) {
if(mIsBaseLayoutManager) {
final BaseLayoutManager layoutManager = (BaseLayoutManager) getLayoutManager();
return (layoutManager.cannotScrollBackward(value)
|| layoutManager.cannotScrollForward(value));
}
return false;
}
/**
* 通过Margins来设置布局的横纵间距;
* (与addItemDecoration()方法可二选一)
* @param verticalSpacing
* @param horizontalSpacing
*/
public void setSpacingWithMargins(int verticalSpacing, int horizontalSpacing) {
this.mVerticalSpacingWithMargins = verticalSpacing;
this.mHorizontalSpacingWithMargins = horizontalSpacing;
if(mIsBaseLayoutManager) {
BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
layout.setSpacingWithMargins(verticalSpacing, horizontalSpacing);
}
adjustPadding();
}
/**
* 根据Margins调整Padding值
*/
private void adjustPadding() {
if((mVerticalSpacingWithMargins > 0 || mHorizontalSpacingWithMargins > 0)) {
final int verticalSpacingHalf = mVerticalSpacingWithMargins / 2;
final int horizontalSpacingHalf = mHorizontalSpacingWithMargins / 2;
final int l = getPaddingLeft() - verticalSpacingHalf;
final int t = getPaddingTop() - horizontalSpacingHalf;
final int r = getPaddingRight() - verticalSpacingHalf;
final int b = getPaddingBottom() - horizontalSpacingHalf;
setPadding(l, t, r, b);
}
}
public TwoWayLayoutManager.Orientation getOrientation() {
if(mIsBaseLayoutManager) {
final BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
return layout.getOrientation();
}
else if(getLayoutManager() instanceof LinearLayoutManager) {
final LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();
return layout.getOrientation() == LinearLayoutManager.HORIZONTAL
? BaseLayoutManager.Orientation.HORIZONTAL
: BaseLayoutManager.Orientation.VERTICAL;
}
else {
return BaseLayoutManager.Orientation.VERTICAL;
}
}
public void setOrientation(TwoWayLayoutManager.Orientation orientation) {
if(mIsBaseLayoutManager) {
final BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
layout.setOrientation(orientation);
}
else if(getLayoutManager() instanceof LinearLayoutManager) {
final LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();
layout.setOrientation(orientation == BaseLayoutManager.Orientation.HORIZONTAL
? LinearLayoutManager.HORIZONTAL : LinearLayoutManager.VERTICAL);
}
}
public int getFirstVisiblePosition() {
if(getChildCount() == 0)
return 0;
else
return getChildLayoutPosition(getChildAt(0));
}
public int getLastVisiblePosition() {
final int childCount = getChildCount();
if(childCount == 0)
return 0;
else
return getChildLayoutPosition(getChildAt(childCount - 1));
}
public void scrollToPositionWithOffsetStart(int position) {
scrollToPositionWithOffset(position, mSelectedItemOffsetStart);
}
public void scrollToPositionWithOffset(int position, int offset) {
if(mIsBaseLayoutManager) {
BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
layout.scrollToPositionWithOffset(position, offset);
return;
} else if (getLayoutManager() instanceof LinearLayoutManager) {
((LinearLayoutManager)getLayoutManager()).scrollToPositionWithOffset(position, offset);
return;
}
scrollToPosition(position);
}
int mTempPosition = 0;
@Override
protected int getChildDrawingOrder(int childCount, int i) {
View view = getFocusedChild();
if(null != view) {
mTempPosition = getChildLayoutPosition(view) - getFirstVisiblePosition();
if (mTempPosition < 0) {
return i;
} else {
if (i == childCount - 1) {//这是最后一个需要刷新的item
if (mTempPosition > i) {
mTempPosition = i;
}
return mTempPosition;
}
if (i == mTempPosition) {//这是原本要在最后一个刷新的item
return childCount - 1;
}
}
}
return i;
}
public boolean isScrolling() {
return getScrollState() != SCROLL_STATE_IDLE;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean result = super.dispatchKeyEvent(event);
if(!result) {
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
result = onKeyDown(event.getKeyCode(), event);
break;
case KeyEvent.ACTION_UP:
result = onKeyUp(event.getKeyCode(), event);
break;
}
}
return result;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean reuslt = super.onKeyDown(keyCode, event);
// 拦截key事件
if(mIsInterceptKeyEvent && !reuslt) {
reuslt = handleOnKey(keyCode, event);
}
return reuslt;
}
/**
* 处理onKeyDown等事件
* @param keyCode
* @param event
* @return
*/
private boolean handleOnKey(int keyCode, KeyEvent event) {
int direction = keyCode2Direction(keyCode);
if(direction == -1) {
return false;
}
else if(hasInBorder(direction)) {
return null != mOnInBorderKeyEventListener && mOnInBorderKeyEventListener.onInBorderKeyEvent(direction, keyCode, event);
}
else {
View newFocusedView = findNextFocus(direction);
if (null != newFocusedView) {
newFocusedView.requestFocus();
}
}
return true;
}
/**
* 查找下个可获取焦点的view
* @param direction
* @return
*/
private View findNextFocus(int direction) {
return FocusFinder.getInstance().findNextFocus(this, getFocusedChild(), direction);
}
/**
* keycode值转成Direction值
* @param keyCode
* @return
*/
private int keyCode2Direction(int keyCode) {
switch (keyCode){
case KeyEvent.KEYCODE_DPAD_DOWN:
return FOCUS_DOWN;
case KeyEvent.KEYCODE_DPAD_RIGHT:
return FOCUS_RIGHT;
case KeyEvent.KEYCODE_DPAD_LEFT:
return FOCUS_LEFT;
case KeyEvent.KEYCODE_DPAD_UP:
return FOCUS_UP;
default:
return -1;
}
}
@Override
public View focusSearch(View focused, int direction) {
if(hasInBorder(direction)) {
return super.focusSearch(focused, direction);
} else {
return findNextFocus(direction);
}
}
/**
* 判断选中的item是否到达边界
* @param direction
* @return
*/
private boolean hasInBorder(int direction) {
boolean result = false;
final View view = getFocusedChild();
if(null != view) {
Rect outRect = new Rect();
getLayoutManager().calculateItemDecorationsForChild(view, outRect);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
switch (direction) {
case FOCUS_DOWN:
result = getHeight() - view.getBottom() <= getPaddingBottom() + lp.bottomMargin + outRect.bottom;
if(isVertical()) {
result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1);
}
break;
case FOCUS_UP:
result = view.getTop() <= getPaddingTop() + lp.topMargin + outRect.top;
if(isVertical()) {
result = result && getFirstVisiblePosition() == 0;
}
break;
case FOCUS_LEFT:
result = view.getLeft() <= getPaddingLeft() + lp.leftMargin + outRect.left;
if(!isVertical()) {
result = result && getFirstVisiblePosition() == 0;
}
break;
case FOCUS_RIGHT:
result = getWidth() - view.getRight() <= getPaddingRight() + lp.rightMargin + outRect.right;
if(!isVertical()) {
result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1);
}
break;
}
}
return result;
}
@Override
public void onChildAttachedToWindow(View child) {
if(!ViewCompat.hasOnClickListeners(child)) {
child.setOnClickListener(mItemListener);
}
if(null == child.getOnFocusChangeListener()) {
child.setOnFocusChangeListener(mItemListener);
}
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
if(null == getFocusedChild()) {
requestDefaultFocus();
}
int descendantFocusability = getDescendantFocusability();
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:
return true;
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = true;
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
case FOCUS_AFTER_DESCENDANTS: {
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
return took ? took : super.requestFocus(direction, previouslyFocusedRect);
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
// Log.i(LOGTAG, "onFocusChanged..." + gainFocus + " ,direction="+direction + " ,mOldSelectedPosition="+mOldSelectedPosition);
mHasFocus = gainFocus;
if(gainFocus) {
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
} else {
setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
}
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
public void setItemActivated(int position) {
if(mIsMenu) {
ViewHolder holder;
if(position != mPreSelectedPosition) {
holder = findViewHolderForLayoutPosition(mPreSelectedPosition);
if(null != holder && holder.itemView.isActivated()) {
holder.itemView.setActivated(false);
}
}
holder = findViewHolderForLayoutPosition(position);
if(null != holder && !holder.itemView.isActivated()) {
holder.itemView.setActivated(true);
mPreSelectedPosition = position;
mSelectedPosition = position;
}
}
}
@Override
public boolean hasFocus() {
// Log.i(LOGTAG, "hasFocus...");
return super.hasFocus();
}
@Override
public boolean isInTouchMode() {
// Log.i(LOGTAG, "isInTouchMode...");
boolean result = super.isInTouchMode();
// 解决4.4版本抢焦点的问题
if (Build.VERSION.SDK_INT == 19) {
return !(hasFocus() && !result);
} else {
return result;
}
}
@Override
public boolean isInEditMode() {
return true;
}
@Override
protected Parcelable onSaveInstanceState() {
RecyclerView.SavedState superSavedState = (RecyclerView.SavedState) super.onSaveInstanceState();
ISavedState savedState = new ISavedState(superSavedState.getSuperState());
savedState.mISuperState = superSavedState;
savedState.mSelectedPosition = mSelectedPosition;
savedState.mPreSelectedPosition = mPreSelectedPosition;
savedState.mVerticalSpacingWithMargins = mVerticalSpacingWithMargins;
savedState.mHorizontalSpacingWithMargins = mHorizontalSpacingWithMargins;
savedState.mSelectedItemOffsetStart = mSelectedItemOffsetStart;
savedState.mSelectedItemOffsetEnd = mSelectedItemOffsetEnd;
savedState.mSelectedItemCentered = mSelectedItemCentered;
savedState.mIsBaseLayoutManager = mIsBaseLayoutManager;
savedState.mIsInterceptKeyEvent = mIsInterceptKeyEvent;
savedState.mIsMenu = mIsMenu;
savedState.mHasMore = mHasMore;
savedState.mIsSelectFirstVisiblePosition = mIsSelectFirstVisiblePosition;
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if(null != state) {
if(state instanceof ISavedState) {
ISavedState savedState = (ISavedState) state;
mSelectedPosition = savedState.mSelectedPosition;
mPreSelectedPosition = savedState.mPreSelectedPosition;
mVerticalSpacingWithMargins = savedState.mVerticalSpacingWithMargins;
mHorizontalSpacingWithMargins = savedState.mHorizontalSpacingWithMargins;
mSelectedItemOffsetStart = savedState.mSelectedItemOffsetStart;
mSelectedItemOffsetEnd = savedState.mSelectedItemOffsetEnd;
mSelectedItemCentered = savedState.mSelectedItemCentered;
mIsBaseLayoutManager = savedState.mIsBaseLayoutManager;
mIsInterceptKeyEvent = savedState.mIsInterceptKeyEvent;
mIsMenu = savedState.mIsMenu;
mHasMore = savedState.mHasMore;
mIsSelectFirstVisiblePosition = savedState.mIsSelectFirstVisiblePosition;
try {
super.onRestoreInstanceState(savedState.mISuperState);
} catch (Exception e) {
e.printStackTrace();
}
} else {
super.onRestoreInstanceState(state);
}
}
}
protected static class ISavedState extends android.view.View.BaseSavedState {
private int mSelectedPosition;
private int mPreSelectedPosition;
private int mVerticalSpacingWithMargins;
private int mHorizontalSpacingWithMargins;
private int mSelectedItemOffsetStart;
private int mSelectedItemOffsetEnd;
private boolean mSelectedItemCentered;
private boolean mIsBaseLayoutManager;
private boolean mIsInterceptKeyEvent;
private boolean mIsMenu;
private boolean mHasMore;
private boolean mIsSelectFirstVisiblePosition;
private Parcelable mISuperState;
protected ISavedState(Parcelable superState) {
super(superState);
}
protected ISavedState(Parcel in) {
super(in);
mISuperState = in.readParcelable(RecyclerView.class.getClassLoader());
mSelectedPosition = in.readInt();
mPreSelectedPosition = in.readInt();
mVerticalSpacingWithMargins = in.readInt();
mHorizontalSpacingWithMargins = in.readInt();
mSelectedItemOffsetStart = in.readInt();
mSelectedItemOffsetEnd = in.readInt();
boolean[] booleens = new boolean[6];
in.readBooleanArray(booleens);
mSelectedItemCentered = booleens[0];
mIsBaseLayoutManager = booleens[1];
mIsInterceptKeyEvent = booleens[2];
mIsMenu = booleens[3];
mHasMore = booleens[4];
mIsSelectFirstVisiblePosition = booleens[5];
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeParcelable(mISuperState, 0);
out.writeInt(mSelectedPosition);
out.writeInt(mPreSelectedPosition);
out.writeInt(mVerticalSpacingWithMargins);
out.writeInt(mHorizontalSpacingWithMargins);
out.writeInt(mSelectedItemOffsetStart);
out.writeInt(mSelectedItemOffsetEnd);
boolean[] booleens = {mSelectedItemCentered, mIsBaseLayoutManager,
mIsInterceptKeyEvent, mIsMenu, mHasMore, mIsSelectFirstVisiblePosition};
out.writeBooleanArray(booleens);
}
public static final Creator<ISavedState> CREATOR
= new Creator<ISavedState>() {
@Override
public ISavedState createFromParcel(Parcel in) {
return new ISavedState(in);
}
@Override
public ISavedState[] newArray(int size) {
return new ISavedState[size];
}
};
}
public void setOnItemListener(OnItemListener onItemListener) {
mOnItemListener = onItemListener;
}
public void setOnInBorderKeyEventListener(OnInBorderKeyEventListener onInBorderKeyEventListener) {
mOnInBorderKeyEventListener = onInBorderKeyEventListener;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
mOnLoadMoreListener = onLoadMoreListener;
}
public interface OnLoadMoreListener {
boolean onLoadMore();
}
public interface OnInBorderKeyEventListener {
boolean onInBorderKeyEvent(int direction, int keyCode, KeyEvent event);
}
public interface OnItemListener {
void onItemPreSelected(TvRecyclerView parent, View itemView, int position);
void onItemSelected(TvRecyclerView parent, View itemView, int position);
void onReviseFocusFollow(TvRecyclerView parent, View itemView, int position);
void onItemClick(TvRecyclerView parent, View itemView, int position);
}
private interface ItemListener extends View.OnClickListener, View.OnFocusChangeListener {
}
}