RecyclerView焦点处理
近期在做一个TV项目,焦点的问题处理比较麻烦。在网上搜索都找不到这类处理的方法。所以分享一个处理方法给大家。重写了一个RecyclerView和FrameLayout。详情请大家去细看,我这里就不赘述。代码如下:
1、布局
activity_main
public class RecyclerViewAdapter extends RecyclerView.Adapter{
private static final String TAG = RecyclerViewAdapter.class.getSimpleName();
private Context mContext;
private List data;
private int mCurPosition = 0;
private int layoutManager = LAYOUT_MANAGER_LINEAR;
public static final int LAYOUT_MANAGER_LINEAR = 0;
public static final int LAYOUT_MANAGER_GRID = 1;
public static final int LAYOUT_MANAGER_LINEAR_1 = 2;
public static final int LAYOUT_MANAGER_GRID_1 = 3;
public RecyclerViewAdapter(Context context, List data, int layoutManager) {
this.mContext = context;
this.data = data;
this.layoutManager = layoutManager;
}
public void setData(List data) {
this.data = data;
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
view = LayoutInflater.from(mContext).inflate(R.layout.item_layout_nomal, parent, false);
return new Holder(view);
}
@Override
public void onBindViewHolder(final Holder holder, final int position) {
holder.title.setText(data.get(position).name);
holder.itemView.setFocusable(true);
holder.itemView.setTag(position);
holder.thumbnail.setImageResource(R.mipmap.test_bg);
FrameLayout.LayoutParams params = null;
switch (layoutManager) {
case LAYOUT_MANAGER_LINEAR:
params = setLayoutSize(490,262,0,0,10,10);
break;
case LAYOUT_MANAGER_GRID:
params = setLayoutSize(365,200,0,0,10,0);
break;
case LAYOUT_MANAGER_LINEAR_1:
params = setLayoutSize(250,160,0,0,10,0);
break;
case LAYOUT_MANAGER_GRID_1:
params = setLayoutSize(450,260,0,0,10,10);
break;
}
holder.root.setLayoutParams(params);
if (mOnItemKeyListener != null) {
holder.itemView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return mOnItemKeyListener.onKey(v,keyCode,event);
}
});
}
if (mOnItemFocusChangeListener != null) {
holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
Log.i(TAG, "has focus:" + position + "--" + hasFocus);
mCurPosition = (int) holder.itemView.getTag();
mOnItemFocusChangeListener.onItemFocusChangeListener(holder.itemView, hasFocus, mCurPosition);
}
});
}
if(mOnItemClickListener !=null){
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(v,holder.getLayoutPosition());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mOnItemClickListener.onItemLongClick(v, holder.getLayoutPosition());
return true;
}
});
}
}
@NonNull
private FrameLayout.LayoutParams setLayoutSize(int width,int height,int left,int top,int right,int bottom) {
FrameLayout.LayoutParams params;
// width = matrixing(width);
// height = matrixing(height);
params = new FrameLayout.LayoutParams(width, height);
params.setMargins(left, top, right, bottom);
return params;
}
private int matrixing(int num) {
return ((int) (num * 2f / 3));
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
class Holder extends RecyclerView.ViewHolder {
TextView title;
ImageView thumbnail;
SWFrameLayout root;
Holder(View view) {
super(view);
root = ((SWFrameLayout) view.findViewById(R.id.root));
title = ((TextView) view.findViewById(R.id.text));
thumbnail = ((ImageView) view.findViewById(R.id.image));
}
}
/*设置item点击事件的接口*/
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
/*设置item选中的接口*/
public interface OnItemFocusChangeListener{
void onItemFocusChangeListener(View v, boolean hasFocus, int position);
}
/*设置item的onKey事件的接口*/
public interface OnItemKeyListener {
boolean onKey(View v, int keyCode, KeyEvent event);
}
private OnItemClickListener mOnItemClickListener;
private OnItemFocusChangeListener mOnItemFocusChangeListener;
private OnItemKeyListener mOnItemKeyListener;
public void setOnItemFocusChangeListener(OnItemFocusChangeListener listener){
mOnItemFocusChangeListener = listener;
}
public void setOnItemClickListener(OnItemClickListener listener){
mOnItemClickListener = listener;
}
public void setOnItemKeyListener(OnItemKeyListener listener) {
mOnItemKeyListener = listener;
}
}
3、MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener,RecyclerViewAdapter.OnItemFocusChangeListener {
private static final String TAG = MainActivity.class.getName();
private SWRecyclerView mRecyclerView;
private ImageView mHeadIcon;
private TextView mName;
private TextView mIntroduce;
private ImageView mPlay;
private ImageView mLike;
private ImageView mCollect;
private List mData;
private Bean danceBean;
private LinearLayoutManager mLayoutManager;
private RecyclerViewAdapter mDanceTypeAdapter;
private int mIVFlag = 0;
private int mRVFlag = 0;
private int mRVCurrentPosition;
private long mMediaDetailId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initAdapter();
}
protected void initView() {
mRecyclerView = ((SWRecyclerView) findViewById(R.id.recycler_view));
mHeadIcon = ((ImageView) findViewById(R.id.head_icon));
mPlay = ((ImageView) findViewById(R.id.iv_play));
mLike = ((ImageView) findViewById(R.id.iv_like));
mCollect = ((ImageView) findViewById(R.id.iv_collect));
mName = ((TextView) findViewById(R.id.tv_name));
mIntroduce = ((TextView) findViewById(R.id.tv_introduce));
mPlay.setOnClickListener(this);
mLike.setOnClickListener(this);
mCollect.setOnClickListener(this);
}
protected void initData() {
mData = new ArrayList<>();
for (int i = 0; i < 6; i++) {
mData.add(new Bean("我爱北京天门-" + i));
}
}
private void initAdapter() {
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mDanceTypeAdapter = new RecyclerViewAdapter(this, mData, RecyclerViewAdapter.LAYOUT_MANAGER_LINEAR_1);
mDanceTypeAdapter.setOnItemFocusChangeListener(this);
mPlay.requestFocus();
useLinearLayout();
}
private void useLinearLayout() {
mRecyclerView.setAdapter(mDanceTypeAdapter);
mRecyclerView.setLayoutManager(mLayoutManager);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean ret = false;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
if (mPlay.hasFocus() || mLike.hasFocus() || mCollect.hasFocus()) { //如果是三个图标获得焦点按上键不会下发
ret = true;
}else {
switch (mIVFlag) { //判断上次失去焦点是哪个图标
case 0:
mPlay.requestFocus();
break;
case 1:
mLike.requestFocus();
break;
case 2:
mCollect.requestFocus();
break;
}
ret = false;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mPlay.hasFocus()) { //失去焦点时标记
mIVFlag = 0;
} else if (mLike.hasFocus()) {
mIVFlag = 1;
} else {
mIVFlag = 2;
}
mRecyclerView.setDefaultSelect(mRVCurrentPosition); //设置RecyclerView获得焦点的位置,首次是默认首个item获得焦点
ret = false;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mPlay.hasFocus()) { //如果是播放图标获得焦点按左键就不会下发
ret = true;
} else {
ret = false;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (mCollect.hasFocus()) { //如果是收藏图标获得焦点按右键就不会下发
ret = true;
} else {
ret = false;
}
break;
case KeyEvent.KEYCODE_MENU:
ret = true;
break;
case KeyEvent.KEYCODE_BACK:
finish();
ret = true;
break;
default:
break;
}
return ret;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_play:
Log.i("play", "点击了play");
Toast.makeText(this, "点击了播放", Toast.LENGTH_SHORT).show();
break;
case R.id.iv_like:
Log.i("play", "点击了like");
break;
case R.id.iv_collect:
Log.i("play", "点击了collect");
break;
}
}
@Override
public void onItemFocusChangeListener(View v, boolean hasFocus, int position) {
mRVCurrentPosition = position; //记录失去焦点的位置
if (v instanceof SWFrameLayout)
((SWFrameLayout)v).onFocusChange(v, hasFocus); //被拦截,需要手动开启,不然item不能放大。
}
}
4、自定义View
public class SWFrameLayout extends FrameLayout implements View.OnFocusChangeListener, View.OnClickListener{
private static final String TAG = SWFrameLayout.class.getSimpleName();
protected static final int ANIMATION_DURATION = 300;
protected static final float FOCUS_SCALE = 1.1f;
protected static final float NORMAL_SCALE = 1.0f;
private boolean mFocus = true;
public SWFrameLayout(Context context) {
this(context, null, 0);
}
public SWFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SWFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SWFrameLayout, defStyleAttr, 0);
mFocus = typedArray.getBoolean(R.styleable.SWFrameLayout_focus, true);
init();
typedArray.recycle();
}
protected void init() {
setOnClickListener(this);
setOnFocusChangeListener(this);
setBackgroundResource(R.drawable.home_fragment_item_selector);
setFocusable(mFocus);
setFocusableInTouchMode(mFocus);
}
@Override
public void onClick(View view) {
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
Log.i(TAG, "onFocusChanged: gainFocus = "+gainFocus+", direction = "+direction);
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (null == v){
Log.i(TAG, "onFocusChange: v == null");
return;
}
Log.i(TAG, "onFocusChange: hasFocus = " +hasFocus);
if (hasFocus) {
v.bringToFront();
v.animate().scaleX(FOCUS_SCALE).scaleY(FOCUS_SCALE).setDuration(ANIMATION_DURATION).start();
v.setSelected(true);
} else {
v.animate().scaleX(NORMAL_SCALE).scaleY(NORMAL_SCALE).setDuration(ANIMATION_DURATION).start();
v.setSelected(false);
}
}
}
public class SWRecyclerView extends RecyclerView {
private static final String TAG = SWRecyclerView.class.getSimpleName();
private boolean mSelectedItemCentered = true;
private int mSelectedItemOffsetStart;
private int mSelectedItemOffsetEnd;
private int offset = -1;
private int mDuration = 0;
public SWRecyclerView(Context context) {
super(context);
}
public SWRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SWRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void requestChildFocus(View child, View focused) {
// 获取焦点框居中的位置
if (null != child) {
if (mSelectedItemCentered) {
mSelectedItemOffsetStart = !isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight());
mSelectedItemOffsetStart /= 2;
mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
}
}
super.requestChildFocus(child, focused);
}
@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;
}
offset = isVertical() ? dy : dx;
if (dx != 0 || dy != 0) {
if (mDuration == 0)
smoothScrollBy(dx, dy);
else
smoothScrollBy(dx, dy, mDuration);
Log.i(TAG, "requestChildRectangleOnScreen: immediate--->"+immediate);
return true;
}
// 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法
postInvalidate();
return false;
}
private int getFreeWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getFreeHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
/**
* 设置默认选中.
*/
public void setDefaultSelect(int pos) {
ViewHolder vh = (ViewHolder) findViewHolderForAdapterPosition(pos);
requestFocusFromTouch();
if (vh != null)
vh.itemView.requestFocus();
}
/**
* 设置SWRecyclerView的滑动速度
* @param duration
*/
public void setDuration(int duration){
mDuration = duration;
}
/**
* 设置选中的Item居中;
* @param isCentered
*/
public void setSelectedItemAtCentered(boolean isCentered) {
this.mSelectedItemCentered = isCentered;
}
/**
* 判断是垂直,还是横向.
*/
private boolean isVertical() {
LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();
return layout.getOrientation() == LinearLayoutManager.VERTICAL;
}
/**
* 利用反射拿到RecyclerView中的mViewFlinger属性,
* 再调用其smoothScrollBy(int dx, int dy, int duration) 方法实现RecyclerViewTV速度的控制
* @param dx
* @param dy
* @param duration
*/
public void smoothScrollBy(int dx, int dy, int duration) {
try {
Class> c = null;
try {
c = Class.forName("android.support.v7.widget.RecyclerView");//获得Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
Field mLayoutField = c.getDeclaredField("mLayout"); //根据属性名称,获得类的属性成员Field
mLayoutField.setAccessible(true); //设置为可访问状态
LayoutManager mLayout = null;
try {
mLayout = (LayoutManager) mLayoutField.get(this); //获得该属性对应的对象
if(mLayout == null){
return;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
return;
}
Field mLayoutFrozen = c.getDeclaredField("mLayoutFrozen");
mLayoutFrozen.setAccessible(true);
try {
if((Boolean)mLayoutFrozen.get(this)){
return;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
return;
}
if (!mLayout.canScrollHorizontally()) {
dx = 0;
}
if (!mLayout.canScrollVertically()) {
dy = 0;
}
Field mViewFlingerField = c.getDeclaredField("mViewFlinger");
mViewFlingerField.setAccessible(true);
try {
Class> ViewFlingerClass = null;
try {
//由于内部类是私有的,所以不能直接得到内部类名,
//通过mViewFlingerField.getType().getName()
//可以得到私有内部类的完整类名
ViewFlingerClass = Class.forName(mViewFlingerField.getType().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
//根据方法名,获得我们的目标方法对象。第一个参数是方法名,后面的是该方法的入参类型。
// 注意Integer.class与int.class的不同。
Method smoothScrollBy = ViewFlingerClass.getDeclaredMethod("smoothScrollBy",
int.class, int.class, int.class);
smoothScrollBy.setAccessible(true);//设置为可操作状态
if (dx != 0 || dy != 0) {
Log.d("MySmoothScrollBy", "dx="+dx + " dy="+dy);
try {
//唤醒(调用)方法,
// mViewFlingerField.get(this)指明是哪个对象调用smoothScrollBy。
// dx, dy, duration 是smoothScrollBy所需参数
smoothScrollBy.invoke(mViewFlingerField.get(this), dx, dy, duration);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
return;
}
}catch (NoSuchFieldException e){
return;
}
}
}
5、资源文件
home_fragment_item_selector focus是一张结尾.9的透明图片
播放、收藏、点赞这个三个图标随意用其它图片代替
获得焦点切换不同的图片
时间紧任务重,展示效果不是很好。凑合着看哈,重要的是处理方法。有兴趣的朋友可以试试。有不同见解请留言。互相学习。觉得不错就点个赞,谢谢...