본문 바로가기
프로젝트/Roulette Wheel View

[Roulette] 룰렛을 그리고 회전시키기 (4) - 사용 편의성 개선

by JhDroid 2021. 3. 13.
728x90

 

일부 속성 값을 사용자한테 입력받기

  • 일부 속성의 값은 사용자에게 입력받는 것이 좋습니다.
    • 텍스트 크기, 배경 원의 선 색상, 텍스트 색상 등
  • 사용자에게 속성을 입력받기 위해 커스텀 뷰에서 사용할 커스텀 속성을 추가해야합니다.
  • 사용자에게 값을 받을 수 있는 방법은 2가지 입니다.
    • Activity/Fragment에서 전달받기
    • xml(layout)에서 전달받기

 

0.  커스텀 속성 추가

  • 자신의 커스텀 뷰에서 사용할 속성(Size, Color 등)을 정의할 수 있습니다.
  • 커스텀 속성의 경우 values 디렉토리의 attrs.xml에 생성합니다.(안드로이드 뷰 클래스 가이드 참고)
    • attrs.xml의 경우 프로젝트 생성 시 자동으로 생성되지 않으니 생성해주세요!
  • attrs.xml에 아래와 같이 커스텀 속성을 추가했습니다.
<resources xmlns:tools="http://schemas.android.com/tools">
    <declare-styleable name="RouletteView">
        <attr name="rouletteBorderLineColor" format="reference|color"/>
        <attr name="textColor" format="reference|color"/>
        <attr name="rouletteSize" format="integer"/>
        <attr name="textSize" format="reference|dimension"/>
        <attr name="emptyMessage" format="string"/>
        <attr name="rouletteBorderLineWidth" format="reference|dimension"/>
    </declare-styleable>
</resources>
  • <attr> 태그를 통해 커스텀 뷰에서 사용할 속성을 정의합니다.
    • reference의 경우 xml에서 id값으로 추가가 가능하게합니다.
      • ex) @color/white

 

1. xml(layout)에서 전달받기

  • CustomView 클래스에서 typedArray 객체를 통해 해당 스타일을 사용한다고 설정합니다.
class Roulette @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    ...

    init {
        val typedArray = context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.RouletteView,
            defStyleAttr,
            0
        )

        ...   
    }

 

  • 위에서 생성한 typedArray 객체를 통해 각 속성을 사용자가 추가한 데이터를 전달받을 수 있도록 하고 사용자가 아무것도 입력하지 않으면 기본값으로 초기화하도록 설정해줍니다.
class Roulette @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var textSize = 0f
    private var textColor = Color.BLACK
    private var rouletteBorderLineColor = Color.BLACK
    private var rouletteBorderLineWidth = 0f
    
    ...

    init {
        val typedArray = context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.RouletteView,
            defStyleAttr,
            0
        )

        rouletteBorderLineColor = typedArray.getColor(
            R.styleable.RouletteView_rouletteBorderLineColor,
            Color.BLACK
        )

        rouletteBorderLineWidth = typedArray.getDimension(
            R.styleable.RouletteView_rouletteBorderLineWidth,
            20f
        )

        textColor = typedArray.getColor(
            R.styleable.RouletteView_textColor,
            Color.BLACK
        )
        
        textSize = typedArray.getDimension(
            R.styleable.RouletteView_textSize,
            60f
        )
        
        typedArray.recycle()
        ...
    }
  • 코드 설명
    • getColor()/getDimension()/getColor()/getDimension()
      • 사용한 4개의 함수말고도 getBoolean()이나 getFloat()등이 있으며 위에서 추가한 속성에 맞게 사용해 변수를 초기화합니다.
      • 파라미터는 대부분의 함수가 비슷합니다. getXXX(int index, <Type> defaultValue)
      • int index
        • index는 declare-styleable을 추가하면 자동으로 생성되는 <declare-styleable_name>_<attr_name>를 추가하면 됩니다.
      • <Type> defaultValue
        • defaultValue는 사용자가 속성을 추가하지 않으면 기본값으로 사용할 값을 추가하면 되며 각 속성에 맞는 타입의 데이터를 설정하면 됩니다.
    • typedArray.recycle()
      • typedArray 객체는 공유 리소스로 사용 후 재사용을 위해 생성자(또는 init{})에서 recycle()을 호출해야합니다.

 

  • xml에서 확인하기
    • app 네임스페이스로 속성을 설정해줄 수 있습니다.
<com.jhdroid.view.Roulette
    android:id="@+id/roulette"
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    app:rouletteSize="3"
    app:textSize="40sp"
    app:textColor="@color/white"
    app:emptyMessage="Hello" />

  • xml에서 직접 정의한 속성을 설정해주면 바로 View가 변하는 것을 확인할 수 있습니다.

 

2. Activity/Fragment에서 전달받기

  • 상황에 따라 xml아니라 Activity/Fragment 등에서 UI를 변경하는 경우가 있기 때문에 사용자가 일부 속성을 코드로 설정할 수 있도록 해야합니다.
  • 1. xml에서 전달받기 과정에서 등록한 속성 변수들의 getter & setter 함수를 생성합니다.
    • 그 후 setter 함수의 `최하단`에 데이터를 받은 후 뷰를 갱신해주기 위해 invalidate() 함수를 추가합니다.
class Roulette @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var textSize = 0f
    private var textColor = Color.BLACK
    private var rouletteBorderLineColor = Color.BLACK
    private var rouletteBorderLineWidth = 0f
    
    ...

    init {
        val typedArray = context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.RouletteView,
            defStyleAttr,
            0
        )

        textColor = typedArray.getColor(
            R.styleable.RouletteView_textColor,
            Color.BLACK
        )
        
        textSize = typedArray.getDimension(
            R.styleable.RouletteView_textSize,
            60f
        )

        rouletteBorderLineColor = typedArray.getColor(
            R.styleable.RouletteView_rouletteBorderLineColor,
            Color.BLACK
        )

        rouletteBorderLineWidth = typedArray.getDimension(
            R.styleable.RouletteView_rouletteBorderLineWidth,
            20f
        )
        
        typedArray.recycle()
        ...
    }
    
    ...
    
    fun setTextSize(textSize: Float) {
        this.textSize = textSize
        invalidate()
    }

    fun getTextSize(): Float = textSize
    
    fun setTextColor(textColor: Int) {
        this.textColor = textColor
        invalidate()
    }

    fun getTextColor(): Int = textColor
    
    fun setRouletteBorderLineColor(borderLineColor: Int) {
        this.rouletteBorderLineColor = borderLineColor
        invalidate()
    }

    fun getRouletteBorderLineColor(): Int = rouletteBorderLineColor
    
    fun setRouletteBorderLineWidth(width: Float) {
        rouletteBorderLineWidth = width
        invalidate()
    }

    fun getRouletteBorderLineWidth(): Float = rouletteBorderLineWidth
}
  • 위 코드 처럼 각 속성에 맞게 변수를 생성하고 getter & setter 함수를 추가하면 Activity/Fragment에서 뷰의 속성을 변경할 수 있습니다.
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.activity = this

        binding.roulette.apply {
            setTextColor(R.color.black)
            setRouletteBorderLineColor(R.color.white)
            
            getTextColor()
            getRouletteTextSize()
        }
    }
    ...
}
  • setter 함수를 통해 속성을 설정할 수 있으며 설정된 속성의 값을 getter 함수를 통해 받아올 수 있습니다.
    • setter의 경우 데이터를 설정함과 동시에 invalidate()를 호출하며 View를 새로 그릴 수 있도록 해줍니다.

 

requestLayout()에 대해

  • 안드로이드 뷰 클래스 가이드를 보면 setter 함수에서 invalidate()함수와 함께 requestLayout() 함수도 호출하는 것을 확인할 수 있는데 invalidate()와 requestLayout()의 차이는 다음 뷰 라이프사이클을 이미지를 참고해주세요. 

  • invalidate()Custom View를 다시 그리게 해줍니다. - onDraw() 재호출
  • requestLayout()Custom View의 레이아웃을 갱신하도록 합니다. - onMeasure() 재호출
  • 일단 지금 상황에서는 requestLayout()을 사용할 필요가 없기 때문에 추가하지 않고 다음에 관련된 내용을 다루게 되면 더 자세한 설명과 함께 추가하는 과정을 알아보겠습니다(__)

 

 

* 글에 틀린 부분이 있으면 댓글 부탁드립니다 :D

728x90