[android] NumberPicker, TimePicker, DatePicker의 textColor

처음에는 별거 아니라고 생각했던 textColor 지정하기가 결국 리플렉션까지 사용하게 될 줄은 몰랐습니다. 저를 3일간 패닉에 빠트린 xxxPicker의 세계로 안내합니다.

NumberPicker계열과 안드로이드스튜디오의 문제

숫자를 선택하는데 도움을 주는 UI위젯인 NumberPicker 는 다음과 같은 형태로 생겼습니다.

Screenshot_2

이 NumberPicker 에 기반을 두고 제작된 위젯이 DatePicker, TimePicker 로 예를 들어 TimePicker 는 다음과 같은 모양입니다.

Screenshot_3

척 보기에도 NumberPicker 를 적당히 묶어서 컨트롤러를 붙여놓은 것처럼 보입니다. 이 넘들은 우선 현재 버전의 안드로이드스튜디오의 레이아웃 디자인모드에서 에러를 일으킵니다.

Screenshot_4

안드로이드 스튜디오의 버그로 이걸 해결하려고 고민하실 필요도 소용도 없습니다. 그냥 NumberPicker 계열을 빼고 전부 작업한 뒤에 xml 에 텍스트모드로 삽입하는게 현재로서의 대안입니다(이걸로 엄청난 삽질을..=.= )

흰색 배경에서 텍스트 색상이 너무 티미한 문제

위에 뜬 캡쳐는 텍스트 색이 검정으로 선명하게 보이지만 실제로 아무런 설정을 하지 않고 삽입하면 다음과 같은 모양으로 보입니다.

Screenshot_5

즉 안보입니다. 따라서 NumberPicker 계에서 텍스트 색을 지정하는 문제는 선택의 문제가 아닌 것입니다. 반드시 해야 하는 일이죠.

우선 생각해보면 가장 간단한 NumberPicker 를 공략해볼 수 있을텐데

  1. 쨌든 내부에는 분명히 EditTextView 같은 넘이 있는 거겠죠.
  2. 근데 다이얼을 돌릴 때 마다 가운데 색과 위 아래 색이 바뀝니다.
  3. 즉 이벤트와 연동하여 텍스트를 바꾸는 기능을 컨트롤러가 갖고 있다고 봐야겠죠.
  4. 더우기 매우 자세히보면 가운데 선택된 색상을 기준으로 흐리게 위 아래 텍스트 색상을 만들어내는 것 같기도 합니다!

걍 쳐다봐도 대충 저 정도가 감지되는군요.

해서 처음에는 그저 자식을 루프돌면서 TextView 를 감지하여 setTextColor를 때려주는 전략을 세웠으나 3, 4번을 고려하면 그래봐야 소용없다는 결론에 도달했습니다.

NumberPicker의 텍스트색문제 해결

수많은 삽질과 서핑의 노력 후 결론은 Paint 객체를 찾아서 색상을 지정해주고 동시에 텍스트색도 지정해주는 수 밖에 없다는 결론에 도달했습니다. NumberPicker를 전부 리플렉션해 본 결과 mSelectorWheelPaint 란 넘이 Paint 라는 이름을 갖고 있어서 이 넘을 공략하기로 맘먹고 서핑해보니 이미 이를 해결한 소스가 돌아다니더군요. 이를 정리한 코드는 다음과 같습니다.

void numberPickerTextColor( NumberPicker $v, int $c ){
	for(int i = 0, j = $v.getChildCount() ; i < j; i++){
		View t0 = $v.getChildAt(i);
		if( t0 instanceof EditText ){
			try{
				Field t1 = $v.getClass() .getDeclaredField( "mSelectorWheelPaint" );
				t1.setAccessible(true);
				((Paint)t1.get($v)) .setColor($c);
				((EditText)t0) .setTextColor($c);
				$v.invalidate();
			}catch(Exception e){}
		}
	}
}

이제 간단히 NumberPicker 의 텍스트 색을 다음과 같이 지정할 수 있게 되었습니다!

numberPickerTextColor( findById(R.id.numPicker), Color.BLACK );

TimePicker와 DatePicker의 텍스트색은?

모양으로 보건데 쨌든 분명히 NumberPicker 를 사용하고 있습니다 ^^; 따라서 내부는 NumberPicker 와 그걸 포함하는 레이아웃일게 자명합니다. 따라서 다음과 같이 처리할 수 있습니다.

void dateTimePickerTextColour( ViewGroup $picker, int $color ){

	for( int i = 0, j = $picker.getChildCount() ; i < j ; i++ ){
		View t0 = (View)$picker.getChildAt(i);

		//NumberPicker는 아까만든 함수로 발라내고
		if(t0 instanceof NumberPicker) numberPickerTextColor( (NumberPicker)t0, $c );

		//아니면 계속 돌아봐
		else if(t0 instanceof ViewGroup) dateTimePickerTextColour( (ViewGroup)t0, $c );
	}
}

이렇게 하면 내부 요소를 전부 컴포지트로 돌면서 NumberPicker 만 골라서 처리해줄 수 있게 됩니다. 응? 근데 짜피 ViewGroup 을 받는데 더욱 일반화하면 되지 않나…싶은..^^;

View레벨의 일반화

결국 View레벨에서 텍스트색상을 지정할 수 있는 함수로 일반화할 수 있게 됩니다.

void textColor( View $v, int $color ){

	if($v instanceof NumberPicker) numberPickerTextColor( (NumberPicker)$v, $color );

	else if($v instanceof TextView) ((TextView)$v).setTextColor($c);

	else if($v isntanceof ViewGroup){

		ViewGroup t0 = (ViewGroup) $v;
		for( int i = 0, j = t0.getChildCount() ; i < j ; i++ )
			textColor( t0.getChildAt(i), $color );

	}
}

결론

역시 뷰에서 쉬운 건 하나도 없다는 교훈을 다시 한 번 느낌입니다. HTML이든 iOS든 유니티든…뷰는 역시 죽음, 내장 컴포넌트는 더 죽음(도메인지식을 더 요구하므로!)

이 레벨로 들어가면 항상 문서도 별로 없고 결국 자바가 리플렉션을 이쁘게 제공안했으면 어떻게 해결하라는건가..멍..안드로이드 소스내려받아서 봐야하나..라고 말하면서 내려받는 중..