2018-10-26 안드로이드 TV Recyclerview 긴 버튼 또는 연속 버튼, 초점 분실

5818 단어
본문 참고 블로그: (1)https://blog.csdn.net/Zou_pl/article/details/77507376 (2)https://blog.csdn.net/zhangyalong_android/article/details/80434387
최근 안드로이드 TV 프로젝트 개발에서는 타임라인 앨범을 보여주기 위해 Recyclerview를 사용해 리모컨을 길게 누르거나 연속으로 빠르게 아래로 누르면 초점을 잃어버린다.
원인 분석
RecyclerView 어댑터를 설정한 후 데이터를 채우면 모든 item의view를 만들지 않습니다. 보통 화면의 Item만 만들 수 있습니다. 키를 길게 누르거나 빠르게 눌렀을 때 Recyclerview는 초점을 가져올 view를 만들지 못해 초점을 잃어버립니다.
해결 방법
두 가지 사고방식이 있다. (1) 버튼 속도를 제어하는 것(개인적으로는 바람직하지 않다. 안드로이드 TV에서 초점 버튼 속도를 제어하는 것 참조) (2) Recyclerview에 LayoutManager를 설정하고 LayoutManager에서 초점을 RecyclerView의 LayoutManager에 제어하는 방법이 있다. onInterceptFocusSearch(View focused, int direction) 이 방법은 초점을 찾는 데 쓰인다.버튼을 길게 누르거나 연속으로 누르면 초점이 날아갈 때 RecyclerView의 LayoutManager를 다시 불러와서 이 방법을 다시 써야 합니다.
public class FocusFixedLinearLayoutManager extends LinearLayoutManager {
    private static final String TAG = "FocusFixedLinearLayoutManager";

    private Context mContext;
    private Object mCaller;

    public FocusFixedLinearLayoutManager(Context context) {
        super(context);
    }

    public FocusFixedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        mContext = context;
    }

    public FocusFixedLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public View onInterceptFocusSearch(View focused, int direction) {
        //        ,       ,  View.FOCUS_LEFT View.FOCUS_RIGHT
        if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) {
            //   ,        
            // int currentPosition = getPosition(focused);
            int currentPosition = getPosition(getFocusedChild());
            int count = getItemCount();
            int lastVisiblePosition = findLastVisibleItemPosition();

            //         1 item( 0  ), 0 item   ,      ,
            //    RecyclerView     ,    Fragment      "  " 
            if (direction == View.FOCUS_UP && currentPosition == 1) {
                Log.i(TAG, "onInterceptFocusSearch: fixed Focus to   ------");
                scrollToPosition(0);
                ((ImgPreviewFragment)mCaller).fixedFocusToTabPhoto();
                return null;
            }

            switch (direction) {
                case View.FOCUS_DOWN:
                    currentPosition++;
                    break;
                case View.FOCUS_UP:
                    currentPosition--;
                    break;
            }

            Log.i(TAG, "onInterceptFocusSearch: current position=" + currentPosition);
            Log.i(TAG, "onInterceptFocusSearch: item count=" + count);
            Log.i(TAG, "onInterceptFocusSearch: lastVisiblePosition=" + lastVisiblePosition);

            if (direction == View.FOCUS_DOWN && currentPosition > lastVisiblePosition) {
                Log.i(TAG, "onInterceptFocusSearch: update...");
                scrollToPosition(currentPosition);
            }
        }

        return super.onInterceptFocusSearch(focused, direction);
    }

    public void setFragmentObject(Object object) {
        mCaller = object;
    }
}

코드에서 Fragment에 관한 코드는 관심을 두지 않아도 된다. 이것은 버튼이 위로 올라갈 때 Recyclerview 맨 위에 미끄러지지 않을 수도 있고 맨 위에 미끄러진 후에tab 제목으로 돌아가지 못할 수도 있는 문제를 해결하는 것이다. 왜냐하면 타임라인의 시간이 첫 번째 Item이고 초점(첫 줄의 그림에 초점이 맞춰져 있기 때문에)이 없기 때문에 맨 위에 미끄러지지 않을 수도 있다.(내 UI 레이아웃은 ViewPager는 4개의 Fragment을 포함하고 Fragment는 tab 제목을 포함하며 Fragment는 Recyclerview를 포함한다)이기 때문에 첫 줄의 그림에 초점을 맞출 때 키 방향을 판단하고 위로 올라가면 scrollToPosition을 사용하여 Recyclerview를 위로 미끄러뜨리고 초점을 tab 제목에 강제로 설정한다.
위와 같은 방법은 일부 장면에서 문제가 있다. 예를 들어 위아래 버튼을 반복해서 누르면 다음 Item이 전시되지 않는 상황이 발생할 수 있다. 초점이 이동한 후에 다음 초점이 부분적으로 전시되어야 초점이 사라지는 현상이 발생하지 않도록 고려한다. 핵 코드는 다음과 같다. onInterceptFocusSearch에 다음과 같은 코드를 추가한다.
            View focusItem = getFocusedChild();
            //    ,              ,       ,       ,         view  ,
            //                  View 
            if (direction == View.FOCUS_UP && nextPosition > 0) {
                Log.i(TAG, "onInterceptFocusSearch: up: scroll to: " + nextPosition);
                if (null != focusItem) {
                    float offsetY = focusItem.getTop();
                    //        top   view   ,     ,    Item       
                    if (offsetY <= mItemHeight) {
                        int finalOffset = mItemHeight - (int)offsetY;
                        scrollToPositionWithOffset(nextPosition, finalOffset);
                    }

                    //        top     view   ,     ,    Item  
                    if (offsetY <= (mItemHeight + mItemHeight / 2) && offsetY > mItemHeight) {
                        int finalOffset = (mItemHeight + mItemHeight / 2) - (int)offsetY;
                        scrollToPositionWithOffset(nextPosition, finalOffset);
                    }
                }
            }

            //   ,      position     Item   ,      ,         ,       
            //          Item 
            if (direction == View.FOCUS_DOWN && nextPosition < getItemCount() - 2) {
                Log.i(TAG, "onInterceptFocusSearch: down: scroll to: " + nextPosition);
                if (null != focusItem) {
                    //        bottom    view     ,     ,    Item       
                    float offsetY =  focusItem.getBottom();
                    if (offsetY >= mItemHeight) {
                        int finalOffset = (int)offsetY - mItemHeight + mItemHeight / 2;
                        scrollToPositionWithOffset(nextPosition, finalOffset);
                    }
                }
            }
        }

좋은 웹페이지 즐겨찾기