descendantFocusability - Child View의 Focus를 제어하자

Posted by ITPangPang
2016. 11. 26. 17:01 안드로이드(android)/위젯(Widget)


descendantFocusability

Child View의 Focus를 제어하자


ㆍ 시작하기 전에 descendantfocusablility라는 이 속성은

    몇몇 특별한 상황에서 필요합니다.


ㆍ 물론 사용하는 방법에 따라 유용하게 쓰일수도 있습니다.


ㆍ 보통 ListView, WebView, VideoView에서 사용됩니다.




오늘은 3번째인

VideoView를 사용할때

문제가 생길 수 있는 상황에서

descendantFocusability 속성을

사용해서 해결해보도록 하겠습니다.


(바쁘신 분들은 글 하단에 해결방법을

보시면 될 것 같습니다.

저는 모든지 실험을 해보는 성격이라..

앞에 내용이 지루할수도 있습니다..)



어떤 상황인지?


VideoView를 사용할 때

ScrollView까지 동시에 사용하는

상황입니다.


그리고 그 VideoView가 상단에

존재하지 않을때가 되겠네요.


그림으로 한번 보면



그림이 참 허접하지만 ㅠㅠ.


이런식으로 VideoView가

최상단에 있지 않은 상황입니다.


xml 코드로 한번 보면

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tistory.itpangpang.resourceex.MainActivity">

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:text="TextView"/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:hint="CheckBox"/>
<VideoView
android:layout_width="match_parent"
android:layout_height="200dp"/>

<TextView
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:text="TextView2"/>
</LinearLayout>
</ScrollView>
</LinearLayout>


이렇게 되겠죠?

이 상태에서 만약

앱을 실행한다면 어떻게 될까요?




위에 gif를 잘 보면

앱을 실행했을때


화면이 최초에 최상단이 아닌

VideoView가 있는곳으로

이동되어있는 것을 확인할 수 있습니다.


여기서 왜 이러한 문제가

생기나 생각해보면

당연히 focus 관련문제겠죠?


확인해 보겠습니다.

앱 실행시 현재 포커스를

체크해보겠습니다.

public class MainActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());
}
}



역시나 예상대로 VideoView에

Focus가 잡힌것을 확인할 수 있습니다.


그럼 간단하게 Focus를 제거하면 될까요?

public class MainActivity extends AppCompatActivity
{
VideoView vv;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vv = (VideoView)findViewById(R.id.vv);
vv.setFocusableInTouchMode(false);
vv.setFocusable(false);

Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());
}
}


이런식으로 먼저 해보겠습니다.



결과는 변한게 없습니다.(실패..)


그럼 이번에는 가장 최상단에

위치하고 있는 TextView에

Focus를 강제로 넣어보겠습니다.


public class MainActivity extends AppCompatActivity
{
VideoView vv;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vv = (VideoView)findViewById(R.id.vv);
tv = (TextView)findViewById(R.id.tv);
/*vv.setFocusable(false);
vv.setFocusableInTouchMode(false);*/
tv.setFocusableInTouchMode(true);
tv.requestFocus();

Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());
}
}



오 Focus가 TextView로 갔습니다.

그럼 해결되었을까요??

오호! 성공입니다.


그런데 어떤 상황에서는

위 방법이 안먹힌다는

얘기도 들었습니다..

(도대체 어떤 상황이길래...)


그래서!!

모든 상황에서

문제해결을 하기 위한

방법을 찾아봤습니다.



문제해결방법!


제목에도 있드시


descendantFocusability

속성을 이용해주는 방법입니다.


이 놈이 어떤속성인가!

한번 보기 위해

Reference를 참고해보겠습니다.


역시나 저도 영어를 못하지만..

느낌정도는 알 수 있기에..


ViewGroup과 그것 자식들(descendants)의

관계(relationship)를 정의한다..

when.. 뷰가 포커스를 갖는것을 발견했을때?


이 정도 느낌이 되겠네요..

(틀릴수도 있는..ㅠㅠ)


여기를 슥~ 한번 보면

value 2가 눈에 확 띕니다.

(blocksDescendants)


ViewGroup will block!

its descendants from receiving focus!!


자식들이 포커스 받는것을

막을 수 있다! 뭐 이런 내용이겠네요


바로 써보겠습니다.


자.. 과연 어디다가 써야할까요?


ScrollView?? - No!

여기서 헷갈리시면 안됩니다.


ScrollView의 바로 밑 자식은

하나 밖에 없습니다

(ScrollView는 자식을

하나밖에 둘 수 없는 것도 중요하죠!)


바로 위치는 ScrollView의 자식인


LinearLayout에 적어줘야 합니다


VideoView는 LinearLayout과

바로 연결되는 자식입니다..

(ScrollView한테는 손자,손녀 정도 되겠네요)


<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:orientation="vertical">


이렇게 써주시면 됩니다.


전체 xml로 보자면

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tistory.itpangpang.resourceex.MainActivity">

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:orientation="vertical">

<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:text="TextView"/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:hint="CheckBox"/>
<VideoView
android:id="@+id/vv"
android:layout_width="match_parent"
android:layout_height="200dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:textSize="30dp"
android:text="TextView2"/>
</LinearLayout>
</ScrollView>
</LinearLayout>


이렇게 됩니다!


결과를 확인해보면?


아!! 잘나옵니다.


문제해결은 여기서 완료했습니다.


하지만 혹시! 위 상황때문에

또 다른 문제점이 생길수 있습니다.

(개발은 어떤상황이 닥칠지 모르니..)


자식들의 포커스를 막아버렸으니? 또 다른 문제점?


혹시 모르니 이것도 적도록 하겠습니다

.

위에서 VideoView와 그 친구들이 있는

부모의 View인 LinearLayout에


android:descendantFocusability="blocksDescendants"


처리를 해놨습니다.


아무 문제가 없을것 같지만.

위 속성을 자세히 생각해보면

앱이 실행될때, 또는 해당 화면에 들어왔을때

!!그때만 막는게 아니라 계속 막는 다는 이야기입니다..


이게 무슨 얘기인가 하면.

상황에 따라서는 requestFocus()를 통해서

특정View에 Focus를 넣을 수도 있는데

위 속성을 넣는다면 이게 불가능 하다는 얘기입니다.


코드로 보자면

public class MainActivity extends AppCompatActivity
{
VideoView vv;
TextView tv;
LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vv = (VideoView)findViewById(R.id.vv);
tv = (TextView)findViewById(R.id.tv);
ll = (LinearLayout)findViewById(R.id.ll);
Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());

tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
tv.setFocusableInTouchMode(true);
tv.requestFocus();

Log.d("ITPANGPANG","request 후 포커스 : " +getCurrentFocus());
}
});
}
}


자 처음 들어올때는

Focus를 막았다고 쳐봅시다.


Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());


이 부분을 보면


이렇게 Focus가 없겠죠?


그런데 상황에 따라서는

TextView를 선택했을때

Focus를 가져오고 싶을 경우가 있습니다.


그래서 위 코드에서 TextView를 click하면

 강제로 requestFocus를 받도록 해놨습니다.


 tv.setFocusableInTouchMode(true);

 tv.requestFocus();

Log.d("ITPANGPANG","request 후 포커스 : " +getCurrentFocus());


결과를 보면


예상과 다르게 

TextView에 Focus가

잡히지 않는 것을 확인 할 수 있습니다.


뭐 엄청 심각한것 처럼 말했지만..

해결방법은 간단합니다..


코드로 descendantfocusability 속성을

원래대로 변경해주면 됩니다.!!


public class MainActivity extends AppCompatActivity
{
VideoView vv;
TextView tv;
LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vv = (VideoView)findViewById(R.id.vv);
tv = (TextView)findViewById(R.id.tv);
ll = (LinearLayout)findViewById(R.id.ll);
Log.d("ITPANGPANG","현재 포커스 : " +getCurrentFocus());

ll.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
tv.setFocusableInTouchMode(true);
tv.requestFocus();
Log.d("ITPANGPANG","request 후 포커스 : " +getCurrentFocus());
}
});
}
}


setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

이렇게 Focus Block을 풀고 싶을때 써주시면 됩니다.


위와 같이 써주고

다시 TextView를 선택해보면


제대로 TextView에 다시

Focus가 잡힌것을 확인 할 수 있습니다.


아! 왜 하필

FOCUS_BEFORE_DESCENDATNTS

적었는가 하면?


위 값이 defalut 값입니다.

xml 속성에서


android:descendantFocusability="??"


이것을 안적어줬을때랑 똑같습니다.


끝!!


*글 내용은 작성자가 직접 테스트해서 나온 결과이므로 틀리는 부분이

있을 수도 있으니 이 점 참고해주세요!!