焦点处理

TV中的遥控器事件

KeyEvent.KEYCODE_DPAD_UP:遥控器上键
KeyEvent.KEYCODE_DPAD_DOWN:遥控器下键
keyEvent.KEYCODE_DPAD_LEFT:遥控器左键
keyEvent.KEYCODE_DPAD_RIGHT:遥控器右键
keyEvent.KEYCODE_DPAD_CENTER:遥控器ok确认键
keyEvent.KEYCODE_DPAD_x:x表示数字0-9,遥控器数字键
KeyEvent.KEYCODE_BACK:遥控器返回键
KeyEvent.KEYCODE_HOME:遥控器主页键
KeyEvent.KEYCODE_x:x表示字母A-Z,遥控器字母键
KeyEvent.KEYCODE_MENU:遥控器菜单键

使用adb命令模拟遥控器按键

adb shell

input keyevent 1  // 遥控器menu键
input keyevent 3  // 遥控器Home键
input keyevent 4  // 遥控器Back键
input keyevent 19 // 遥控器上键
input keyevent 20 // 遥控器下键
input keyevent 21 // 遥控器左键
input keyevent 22 // 遥控器右键
input keyevent 23 // 遥控器ok确认键

焦点拦截事件分发

手机端拦截事件分发:

  • dispatchTouchEvent()

  • onInterceptTouchEvent()

  • onTouchEvent()

TV端拦截事件分发:

  • dispatchKeyEvent()

  • onKeyDown()/onKeyUp()

  • onKeyListener()

事件传递说明

KeyEvent的事件处理只有两个地方,一个是Activity,一个是具体的View,ViewGroup只负责分发事件给子View不消耗事件。

其实Activity和具体View的事件处理都是交由DecorView处理。

(PhoneWindow)$DecorView.dispatchKeyEvent()->
	
Activity.dispatchKeyEvent()->

View.dispatchKeyEvent()->

// setonKeyListener != null则回调监听器
View.setOnKeyListener.onKey()->

// setOnKeyListener == null或onKey()返回false则回调onKeyDown()/onKeyUp()
View.onKeyDown()/onKeyUp()->

// setOnClickListener != null && 遥控器ok按键的ACTION_UP则回调监听器
View.setOnClickListener.onClick()->

// View.setOnClickListener == null则返回到Activity回调onKeyDown()/onKeyUp()
Activity.OnKeyDown()/OnKeyUp()

常见的焦点处理方式

dispatchKeyEvent()

遥控器按下和松开会分别调用两次dispatchKeyEvent():

dispatchKeyEvent->onKeyDown->dispatchKeyEvent->onKeyUp

返回true或false都会拦截事件,即子View将不会接收到分发事件,Activity的onKeyDown()/onKeyUp()也不会接收到

return true:拦截事件,不让焦点再移动
return false:拦截事件,焦点会移动
return super.dispatchKeyEvent:往下分发事件不拦截

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
	int keyCode = event.getKeyCode();
	int action = event.getAction();
	switch(keyCode) {
		case KeyEvent.KEYCODE_DPAD_DOWN:
			if (action == KeyEvent.ACTION_DOWN) {
				Log.i(TAG, "dispatchKeyEvent-ACTION_DOWN");
			} else if (action == KeyEvent.ACTION_UP) {
				Log.i(TAG, "dispatchKeyEvent-ACTION_UP");
			}
			return true;
		case KeyEvent.KEYCODE_DPAD_UP:
			if (action == KeyEvent.ACTION_DOWN) {
				Log.i(TAG, "dispatchKeyEvent-ACTION_DOWN");
			} else if (action == KeyEvent.ACTION_UP) {
				Log.i(TAG, "dispatchKeyEvent-ACTION_UP");
			}
			return false;
	}

	return super.dispatchKeyEvent(event);
}

onKeyDown()/onKeyUp()

事件走到这两个回调表示子View没有消耗事件抛回给上层,在这里做最后的事件处理。

onKeyDown()

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 如果处于booking录制状态,拦截提示退出
        // 将事件抛到onKeyUp处理弹出对话框
        if (isInterceptEventWhenRecord(event)) {
            return super.onKeyDown(keyCode, event);
        }

        if (keyCode == KeyEvent.KEYCODE_0) {
            recordJumpPlayProgNum(0);
            return true;
        }

        if (keyCode == KeyEvent.KEYCODE_1) {
            recordJumpPlayProgNum(1);
            return true;
        }

        if (keyCode == KeyEvent.KEYCODE_2) {
            recordJumpPlayProgNum(2);
            return true;
        }
        ....
        return super.onKeyDown(keyCode, event);
    }

onKeyUp()

 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        // EPG
        if (keyCode == KeyEvent.KEYCODE_F1) {
            gotoEpg();
            return true;
        }

        // TEXT
        if (keyCode == KeyEvent.KEYCODE_TV_TELETEXT) {
            showTeletextDialog();
            return true;
        }

        // RECORD
        if (keyCode == KeyEvent.KEYCODE_MEDIA_RECORD) {
            if (isUsbNotExit()) {
                ToastUtils.showToast(R.string.toast_no_storage_device);
                return true;
            }

            if (DTVBookingManager.getInstance().isRecording()) {
                showQuitRecordDialog(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_UNKNOWN));
            } else {
                if (mProgListAdapter.getCount() > 0) {
                    int recordMinutes = DTVSettingManager.getInstance().getDTVProperty(HSetting_Enum_Property.RecordMaxMin);
                    if (recordMinutes == 0) {
                        showInputPvrMinuteDialog();
                    } else {
                        mRecordSeconds = recordMinutes >= Integer.MAX_VALUE ? Integer.MAX_VALUE : recordMinutes * 60;
                        recordProg();
                    }
                }
            }
            return true;
        }

        // TV/RADIO
        if (keyCode == KeyEvent.KEYCODE_TV_RADIO_SERVICE) {
            stopRecord();
            int currProgType = DTVProgramManager.getInstance().getCurrProgType();
            int group = currProgType == HProg_Enum_Type.GBPROG ? HProg_Enum_Type.TVPROG : HProg_Enum_Type.GBPROG;
            int num = DTVProgramManager.getInstance().getProgNumOfType(group, 0);
            if (num > 0) {
                DTVProgramManager.getInstance().setCurrProgType(currProgType == HProg_Enum_Type.GBPROG ?
                        HProg_Enum_Type.TVPROG : HProg_Enum_Type.GBPROG, 0);
                onProgramUpdate(new ProgramUpdateEvent(true));
            } else {
                ToastUtils.showToast(group == HProg_Enum_Type.GBPROG ? R.string.toast_no_radio : R.string.toast_no_tv);
            }
            return true;
        }

        if (keyCode == KeyEvent.KEYCODE_MENU) {
            dismissPfBarScanDialog();
            if (!mMenuShow) {
                if (mProgListShow) toggleProgList(false); // 隐藏正在显示的频道列表
                mItemInstallation.requestFocus();
                toggleMenu();
            }
            return true;
        }

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mMenuShow && (mIvChannelManageBack.getVisibility() == View.VISIBLE)) {
                //在channelManage界面按BACK返回menu菜单
                restoreMenuItem();
                mItemChannelManage.requestFocus();
            } else if (mMenuShow && (mIvDTVSettingBack.getVisibility() == View.VISIBLE)) {
                //在dtv setting界面按BACK返回menu菜单
                restoreMenuItem();
                mItemDtvSetting.requestFocus();
            } else if (mProgListShow) {
                toggleProgList();
            } else if (isPfBarShowing()) {
                mTvProgNum.setVisibility(View.INVISIBLE);
                dismissPfBarScanDialog();
            } else if (mMenuShow) {
                toggleMenu();
            } else {
                showExitDialog();
            }
            return true;
        }

        return super.onKeyUp(keyCode, event);
    }

setOnKeyListener()

一般实现onKeyListener对具体的遥控器事件做特殊处理

mProgListView.setOnKeyListener((v, keyCode, event) -> {
            if (keyCode == KeyEvent.KEYCODE_PROG_YELLOW && event.getAction() == KeyEvent.ACTION_UP) {
                showFindProgChannelDialog();
                return true;
            }
            return false;
        });

OnClickListener()

一般实现onClickListener都是响应遥控器的ok确认键

new ScanDialog()
                .setOnScanSearchListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(TpListingActivity.this, ScanTVandRadioActivity.class);
                        intent.putExtra(Constants.IntentKey.INTENT_FREQ, getFreq());
                        intent.putExtra(Constants.IntentKey.INTENT_SATELLITE_INDEX, getIndex());
                        intent.putExtra(Constants.IntentKey.INTENT_SYMBOL, getSymbol());
                        intent.putExtra(Constants.IntentKey.INTENT_QAM, getQam());
                        intent.putExtra(Constants.IntentKey.INTENT_SEARCH_TYPE, Constants.IntentValue.SEARCH_TYPE_TPLISTING);
                        startActivity(intent);
                    }
                }).show(getSupportFragmentManager(), ScanDialog.TAG);

View的焦点处理

requestFocus():强制获取焦点

android:focusable:设置一个控件是否能获取焦点

android:nextFocusUp/view.setNextFocusUpId(id):设置遥控器按上键时哪个view会获取到焦点,提供一个id

android:nextFocusDown/view.setNextFocusDownId(id):设置遥控器按下键时哪个view会获取到焦点,提供一个id

android:nextFocusLeft/view.setNextFocusLeftId(id):设置遥控器按左键时哪个view会获取到焦点,提供一个id

android:nextFocusRight/view.setNextFocusRightId(id):设置遥控器按右键时哪个view会获取到焦点,提供一个id

view.setOnFocusChangeListener():监听view获取到焦点,一般在该方法处理view背景修改等

遥控器长按事件的监听

isLongPress只是在apk层面表示是否处于长按事件,但并不会正真拦截事件。

只有return true才可以在系统层面实现正真的拦截下发事件。注意事件下发的顺序是dispatchKeyEvent(event.getRepeatCount==0&ACTION_DOWN)->onKeyDown
->dispatchKeyEvent(event.getRepeatCount>0&ACTION_DOWN)->onKeyDown
->dispatchKeyEvent(ACTION_UP)->onKeyUp

如果在if (event.getRepeatCount() == 0) {}在中return true,则事件不会下发至onKeyDown()中,则对应按下单击皆无响应。
如果在event.getRepeatCount()>0条件中return true,则按下点击多次时只会响应dispatchKeyEvent()中的代码而非onKeyDown()中的代码。
如果在if (event.getRepeatCount() == 0) {}中return true,则按下松开之后只会响应dispatchKeyEvent()中的代码而非onKeyUp()中的代码。

private boolean isLongPress;

@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
		isLongPress = true;
		return true;
	}
	return super.onKeyLongPress(keyCode, event);
}

// dispatchKeyEvent统一处理长按时按下和松开时的处理
// 或者分别在onKeyDown监听长按按下,onKeyUp监听长按松开
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
	int keyCode = event.getKeyCode();
	int action = event.getAction();
	switch(keyCode) {
		case KeyEvent.KEYCODE_DPAD_DOWN:
			if (action == KeyEvent.ACTION_DOWN) {
				// 按下时会执行一次,进行event长按跟踪,不断回调dispatchKeyEvent,第二次开始会走else
				if (event.getRepeatCount() == 0) {
					event.startTracking();
					isLongPress = false;
				} else {
					isLongPress = true;
					// 遥控器向下长按按下处理
					.....
				}
			} else if (action == KeyEvent.ACTION_UP) {
				if (isLongPress) {
					isLongPress = false;
					// 遥控器向下长按松开处理
					....
				}
			}
			return true;
	}
	return super.dispatckKeyEvent(event);
}

	
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
		if (event.getAction() == KeyEvent.ACTION_DOWN) {
			// 按下时会执行一次,进行event长按跟踪
			if (event.getRepeatCount() == 0) {
				isLongPress = false;
				event.startTracking();
			}
			return true;
		}
	}
	return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
		if (isLongPress) {
			isLongPress = false;
			// 遥控器向下长按松开处理
			....
		}
		return true;
	}
	return super.onKeyUp(keyCode, event);
}

如以下代码实现长按频道+键,对此键进行监听。在监听时获取频道信息,在松开时实现切台并播放

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @SuppressLint("RestrictedApi")
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
    
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP || event.getKeyCode() == KeyEvent.KEYCODE_CHANNEL_UP) {
            switch (event.getAction()) {
                case KeyEvent.ACTION_DOWN:
                    if (event.getRepeatCount() == 0) {
                        mLongPressed = false;
                        event.startTracking();
                        mNewProgNum = getCurrentProgNo() + 1;
                        mLongPressDelayTime = System.currentTimeMillis();
                    } else {
                        mLongPressed = true;
                        if (System.currentTimeMillis() - mLongPressDelayTime > 200) {
                            removePlayProgMsg();
                            mLongPressDelayTime = System.currentTimeMillis();
                            if (++mNewProgNum > getLastProgNo()) mNewProgNum = getFirstProgNo();
                            int position = getPositionByProgNum(mNewProgNum);
                            if (position >= 0) {
                                showProgNum(mProgListAdapter.getItem(position).PShowNo, !mLongPressed);
                            }
                        }
                    }
                    break;

                case KeyEvent.ACTION_UP:
                    if (mProgListShow) {
                        return super.dispatchKeyEvent(event);
                    } else {
                        if (mNewProgNum != DTVProgramManager.getInstance().getCurrProgNo()) {
                            if (mLongPressed) {
                                mLongPressed = false;
                                int position = getPositionByProgNum(mNewProgNum);
                                if (position >= 0) {
                                    playProg(mProgListAdapter.getItem(position).ProgNo);
                                }
                            } else {
                                nextProg();
                            }
                        }
                    }

                    mNewProgNum = 0;
                    mLongPressDelayTime = 0;
                    break;
            }
            return true;
        }

        return super.dispatchKeyEvent(event);
        
    }

焦点丢失问题

android:descendantFocusability=“afterDescendants” 在组合控件(即自定义ViewGroup inflate有多个控件的xml布局)焦点处理中,在使用ListView、GridView、RecyclerView时有时会出现焦点丢失问题,需要添加该属性

// ViewGroup会优先其子类控件而获取焦点
android:descendantsFocusability="beforeDescendants"
	
// ViewGroup只有当其子类控件不需要获取焦点时才获取焦点
android:descendantsFocusability="afterDescendants"

// ViewGroup会覆盖子类控件而直接获得焦点
android:descandantsFocusability="blockDescendants"
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager manager = new LinearLayoutManager(this);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(fruitAdapter);
发布了15 篇原创文章 · 获赞 0 · 访问量 235

猜你喜欢

转载自blog.csdn.net/lxy1740/article/details/104292746