android的焦点,android失去焦点事件

android tv常见问题(一)焦点查找规律

Recyclerview聚焦到最后一个Item,继续按下键,焦点保持不变。

凤县网站制作公司哪家好,找创新互联公司!从网页设计、网站建设、微信开发、APP开发、响应式网站等网站项目制作,到程序开发,运营维护。创新互联公司于2013年开始到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联公司

Recyclerview聚焦到最后一个Item,继续按下键,焦点会跳出RecyclerView,跳到附近的View上。

那么当Recyclerview滑动到最底部时,按下键,Android系统是如何找到下一个需要被聚焦的view的呢?我们把断点打在ViewGroup的focusSearch方法上,可以看到从ViewRootImp的performFocusNavigation方法开始,依次调用了如下方法。

View并不会直接去找焦点,而是交给它的parent去找。

焦点会逐级的交给父ViewGroup的focusSearch方法去处理,直到最外层的布局,最后实际上是调用了FocusFinder的findNextFocus方法去寻找新的焦点。

但是这里要注意的是,RecyclerView和其他的ViewGroup不一样,它自己重写了focusSearch方法。所以在焦点查找委托到达到DecorView之前,会先执行RecyclerView的focusSearch方法。

那么,RecyclerView和其他ViewGroup在寻找焦点方面有什么不一样呢? 为什么RecyclerView要重写ViewGroup的焦点查找机制呢 ?想知道这些问题的答案,那我们首先要知道ViewGroup的焦点查找机制。

ViewGroup的焦点查找机制的核心其实就是FocusFinder的findNextFocus方法。

主要步骤:

主要注意三点:

在addFocusables之后,找到指定方向上与当前focused距离最近的view。在进行查找之前,会统一坐标系。

总的来说就是根据当前focused的位置以及按键的方向,循环比较focusable集合中哪一个最适合,然后返回最合适的view,焦点查找就算完成了。

用于比较的方法。分别是将 当前聚焦的view , 当前遍历到的focusable 和 目前为止最合适的focusable (i = 0时是优先级最低的rect)进行比较。

判断是否可以做为候选。可以看作是一个初步筛选的方法,但是到底哪个更好还需要看beamBeat方法,这个方法会将通过筛选的focusable和当前最合适的focusable进行比较,选出更合适的一个。

到这里为止ViewGroup的focusSearch方法基本上就讲完了。那么下面来看一下RecyclerView的focusSearch方法是如何实现焦点查找的。

前面讲到了,该方法主要是为了解决 RecyclerView聚焦在按键方向上、当前屏幕区域内可见的最后一个item时,当前不可见的下一个item将无法获得焦点。

这个方法是由LayoutManager来实现的,这就是RecyclerView的针对上面提到的情况的焦点查找方法。这里主要分析LinearLayoutManager中实现的该方法,如果在使用其他的LayoutManager时出现RecyclelerView焦点不符合预期的话,可以查看对于LayoutManager下的onFocusSearchFailed方法。

主要关注findPartiallyOrCompletelyInvisibleChildClosestToEnd方法,通过这个方法的命名我们大致就可以看出来这个方法的作用了。这个方法主要会 根据当前RecyclerVIew的正逆序以及按键方向,找出最近一个部分或完全不可见的View 。

这个方法是RecyclerView内部的方法,和FocusFinder中的isCandidate方法的逻辑可以说几乎是一摸一样的。

到此为止ViewGroup的focusSearch和RecyclerVIew的focusSearch都分析完了。我们已经知道RecyclerView滑动到最底部的时候,发生了哪些焦点行为,那么解决起来就比较简单了。

结合KeyEvent事件的流转,处理焦点的时机,按照优先级(顺序)依次是:

以上任一处都可以指定焦点,一旦消费了就不再往下走。

比如前面说到了RecyclerView就是通过重写focusSearch方法对边界上部分可见或不可见的view的焦点查找进行了特殊处理。

重写RecyclerView的focusSearch方法

Android移动应用中的焦点分析

简单一点理解,在移动应用中,焦点就是当前正在处理事件的位置。在手机应用中,最有可能用到焦点的就是EditText,如果同一个界面中有多个EditText,通常情况下同一时间只有一个能够输入内容,此时,这个EditText就获取了焦点。

在Android中,对焦点的设置分为两种情况,TouchMode和非TouchMode。现在的手机基本都是触摸屏,我们用手指触摸屏幕来操作Android应用时,处于TouchMode。除了TouchMode之外,还有非TouchMode,利用外接设备来操作应用。比如键盘。使用Genymotion模拟器的时候,一个界面上有多个控件时,可以用电脑tab键来进行移动,被选中的控件会高亮显示,这时候就是非TouchMode,被选中的控件获得了焦点。

在手机应用中,用到焦点的时候并不多,但是TV应用中,需要用遥控器来操作选中控件,这时候就需要对焦点进行处理了。关于焦点,常用方法如下:

在View类中, isFocusable() 和 isFocusableInTouchMode() 获取到的结果都是false,也就是说,直接继承自View的控件是不能获取焦点的。我们常用控件中对这两个方法进行了改写,比如EditText,这两个方法都是true,而Button则只有 isFocusable() 返回true。这也就是为什么我们用tab键选取Button的时候能够高亮显示,而鼠标点击(模拟触控)的时候不能高亮显示的原因了。如果想在点击的时候也能高亮显示Button,需要手动设置 setFocusableInTouchMode(true) ,就可以了。

如果想对控件的焦点状态进行监听,需要设置 setOnFocusChangeListener() ,只要控件的焦点状态发生变化(获得或者失去焦点),都会调用 onFocusChange 方法

关于焦点的移动,默认的算法会寻找指定方向上最近的可以获取焦点的元素(非TouchMode)。另外在创建控件的时候,也可以指定寻找焦点的方向,设置nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp的值为指定元素就可以了。看以下例子:

这里指定了上面的button向上寻找焦点时,下一个元素是id为bottom的元素,也就是说,上面的Button在获取了焦点之后,继续按向上键,系统会将焦点移动到id为bottom的元素上,而不是继续向上。

在开发手机应用的过程中,对焦点的处理并不多,它与事件是两个不同的体系,通常情况下焦点和事件是相互独立并不冲突。但是在Button的点击事件中会有一点问题。如果我们队一个button设置了 setFocusableInTouchMode(true) ,使他可以获取焦点,那么我们点击这个button的时候,第一次点击并不会执行 onClick() 方法,而是执行 onFocusChange() 。第二次点击的时候才会执行 onClick() 方法。看起来好像 onFocusChange() 消耗了点击事件,实际上并不是的。

这个问题我们看一下源码就清楚了:

onClick() 方法是在onTouchEvent的ACTION_UP里调用的,看一下View的onTouchEvent方法:

可以看到,只有当focusTaken为false的时候才会执行onClick,focusTaken的值默认是false的,但是在 isFocusable() isFocusableInTouchMode() !isFocused() 为true的时候,会去 requestFocus 获取焦点,并将值赋给focusTaken。

关键在于 isFocused() ,如果当前Button没有获取焦点, isFocused() 返回false, !isFocused() 值为ture,Button就会去获取焦点,从而导致 focusTaken 为true, onClick 方法就不会执行了,只有Button已经获取了焦点的时候才会执行onClick方法。

Android TV 焦点原理源码解析

相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行。不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手。下面就来探一探Android的焦点分发的过程。

Android焦点事件的分发是从ViewRootImpl的processKeyEvent开始的,源码如下:

源码比较长,下面我就慢慢来讲解一下具体的每一个细节。

dispatchKeyEvent方法返回true代表焦点事件被消费了。

ViewGroup的dispatchKeyEvent()方法的源码如下:

(2)ViewGroup的dispatchKeyEvent执行流程

(3)下面再来瞧瞧view的dispatchKeyEvent方法的具体的执行过程

惊奇的发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true

可以得出结论:如果想要修改ViewGroup焦点事件的分发,可以这么干:

注意:实际开发中,理论上所有焦点问题都可以通过给dispatchKeyEvent方法增加监听来来拦截来控制。

(1)dispatchKeyEvent方法返回false后,先得到按键的方向direction值,这个值是一个int类型参数。这个direction值是后面来进行焦点查找的。

(2)接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。

ViewGroup的findFocus方法如下:

View的findFocus方法

说明:判断view是否获取焦点的isFocused()方法, (mPrivateFlags PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。

其中isFocused()方法的作用是判断view是否已经获取焦点,如果viewGroup已经获取到了焦点,那么返回本身即可,否则通过mFocused的findFocus()方法来找焦点。mFocused其实就是ViewGroup中获取焦点的子view,如果mView不是ViewGourp的话,findFocus其实就是判断本身是否已经获取焦点,如果已经获取焦点了,返回本身。

(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。

(4)focusSearch方法的具体实现。

focusSearch方法的源码如下:

可以看出focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。

(5)FocusFinder是什么?

它其实是一个实现 根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过FocusFinder类来实现的。

(6)FocusFinder是如何通过findNextFocus方法寻找焦点的。

下面就来看看FocusFinder类是如何通过findNextFocus来找焦点的。一层一层往下看,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,且isFocusable = true isInTouchMode() = true的话,FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。

(7)findNextFocus会优先根据XML里设置的下一个将获取焦点的View ID值来寻找将要获取焦点的View。

看看View的findUserSetNextFocus方法内部都干了些什么,OMG不就是通过我们xml布局里设置的nextFocusLeft,nextFocusRight的viewId来找焦点吗,如果按下Left键,那么便会通过nextFocusLeft值里的View Id值去找下一个获取焦点的View。

可以得出以下结论:

1. 如果一个View在XML布局中设置了focusable = true isInTouchMode = true,那么这个View会优先获取焦点。

2. 通过设置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一个焦点。

Android焦点的原理实现就这些。总结一下:

为了方便同志们学习,我这做了张导图,方便大家理解~

Android 音频焦点(Audio Focus)

Android 音频焦点(Audio Focus)

引子

说 Audio Focus 前先说个很简单需求:来电时暂停正在播放的音乐,电话结束时恢复播放。

音频焦点

问题的解决方法就是:请求系统的音频焦点(Request the Audio Focus)。

官方文档指出Android 在处理音频播放是分了多个“音频流”的,如音乐流、音效流、电话声音流等,使控制音量时可以互不干涉。多数情况下我们播放音乐都是使用 STREAM_MUSIC 音频流。

另外,系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何协调,为了避免同时播放音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音乐。

那么,播放音频应该这样来做:

获取音频焦点 requestAudioFocus

获取成功后,开始播放音频

处理音频焦点的丢失和“DUCK”

播放完毕后取消焦点

如此便可以完美的解决引子里的需求。

一个简单的示例

MusicService.java

public class MusicService extends Service {

private AudioManager mAm;

private boolean isPlaymusic;

private String url;

private MediaPlayer mediaPlayer;

@Override

public void onCreate() {

    super.onCreate();

    mAm = (AudioManager) getSystemService(AUDIO_SERVICE);

}

@Override

public void onStart(Intent intent, int startId) {

    if (intent != null) {

        Bundle bundle = intent.getExtras();

        if (bundle != null) {

            isPlaymusic = bundle.getBoolean("isPlay", true);

            url = bundle.getString("url");

            if (isPlaymusic)

                play();

            else

                stop();

        }

    }

}

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {

public void onAudioFocusChange(int focusChange) {

if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {

// Pause playback

pause();

} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {

// Resume playback

resume();

} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {

// mAm.unregisterMediaButtonEventReceiver(RemoteControlReceiver);

mAm.abandonAudioFocus(afChangeListener);

// Stop playback

stop();

}

}

};

private boolean requestFocus() {

// Request audio focus for playback

int result = mAm.requestAudioFocus(afChangeListener,

// Use the music stream.

AudioManager.STREAM_MUSIC,

// Request permanent focus.

AudioManager.AUDIOFOCUS_GAIN);

return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

}

private void resume() {

if (mediaPlayer != null) {

mediaPlayer.start();

}

}

private void pause() {

if (mediaPlayer != null mediaPlayer.isPlaying()) {

mediaPlayer.pause();

}

}

OnCompletionListener completionListener = new OnCompletionListener() {

@Override

public void onCompletion(MediaPlayer player) {

if(!player.isLooping()){

mAm.abandonAudioFocus(afChangeListener);

}

}

};

private void play() {

if (requestFocus()) {

if (mediaPlayer == null) {

try {

mediaPlayer = new MediaPlayer();

mediaPlayer.setDataSource(url);

mediaPlayer.prepare();

mediaPlayer.setOnCompletionListener(completionListener);

} catch (IOException e) {

e.printStackTrace();

}

}

if (!mediaPlayer.isPlaying()) {

mediaPlayer.start();

}

}

}

@Override

public void onDestroy() {

super.onDestroy();

if (mediaPlayer != null)

mediaPlayer.release();

}

private void stop() {

if (mediaPlayer != null) {

mediaPlayer.stop();

}

}

@Override

public IBinder onBind(Intent arg0) {

// TODO Auto-generated method stub

return null;

}

}

经模拟器测试,当来电时音频焦点会给到铃声流,并打出日志:

I/AudioService(1235):  AudioFocus  requestAudioFocus() from AudioFocus_For_Phone_Ring_And_Calls

此时MusicService中的afChangeListener会得到AUDIOFOCUS_LOSS_TRANSIENT,于是会暂停播放音频。

当通话结束或者挂掉电话,afChangeListener会得到AUDIOFOCUS_GAIN,于是恢复播放音频。

注意:

播放完毕一定要禁止掉请求的音频焦点abandonAudioFocus(afChangeListener),否则,如果播放完毕后的某个时段刚好有个通话结束,并且此时没有其他的应用占用了焦点,系统会重新通知服务里的afChangeListener,导致音频再次的播放。

如果丢失的短暂音频焦点允许DUCK状态AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,在这种情况下,应用程序降低音量继续播放,不需要暂停。再次获取后,恢复原来的音量。

Android音频焦点处理方法

前两天在项目上做了对音频焦点使用方法的一个总结,记录在下面。

在Android设备上,默认可以有多个应用同时播放音频,但是,这种处理带来的用户体验并不好,为了解决这个问题,Android引入了音频焦点机制,一次只能有一个App持有音频焦点。

一般情况下,当一个App失去音频焦点时,为了有较好的用户体验,它应该主动暂停播放,从而使新获得音频焦点的App可以清晰的播放音频,避免混音的情况。

以下是官方建议的处理音频焦点应该遵循的一些规则:

处理音频焦点都是通过AudioManager这个类,如下是获得该类实例的方法:

AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

下面介绍音频焦点处理相关的一些方法(不同的Android版本处理音频焦点的方式略有差别,以下内容基于Android 4.4)。

下面进行详细介绍。

2. abandonAudioFocus(OnAudioFocusChangeListener l)

参数同上。

返回值同上。

3. AudioManager.OnAudioFocusChangeListener

当音频焦点发生变化时,可以在OnAudioFocusChangeListener的onAudioFocusChange(int focusChange)方法中监听到,下面详细说明该方法。

onAudioFocusChange(int focusChange)

参数:focusChange可以表明当前音频焦点发生的是何种变化,需要根据该参数状态做出正确的响应。

分为获得和丢失两种情况:

下面是一个申请长音频焦点,播放音乐的例子:

以上


文章名称:android的焦点,android失去焦点事件
标题路径:http://hbruida.cn/article/hoesgh.html