RecyclerView重新得到焦点返回上次失去焦点的位置

RecyclerView焦点处理

 
   近期在做一个TV项目,焦点的问题处理比较麻烦。在网上搜索都找不到这类处理的方法。所以分享一个处理方法给大家。重写了一个RecyclerView和FrameLayout。详情请大家去细看,我这里就不赘述。代码如下:
1、布局
activity_main


    

        

        

            

            

            

                

                

                

            
        

    

    

        
    
 
   
item_layout_nomal


    

    

2、适配器
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的透明图片

    
    
    

播放、收藏、点赞这个三个图标随意用其它图片代替

    
    

获得焦点切换不同的图片 RecyclerView重新得到焦点返回上次失去焦点的位置_第1张图片 RecyclerView重新得到焦点返回上次失去焦点的位置_第2张图片

时间紧任务重,展示效果不是很好。凑合着看哈,重要的是处理方法。有兴趣的朋友可以试试。有不同见解请留言。互相学习。觉得不错就点个赞,谢谢...

你可能感兴趣的:(原创,焦点,RecyclerView,定位)