Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making your life easier with CustomViews

Ygor César
February 09, 2019

Making your life easier with CustomViews

How to use and create Custom Views, reduce boilerplate codes and reuse your custom views

Ygor César

February 09, 2019
Tweet

More Decks by Ygor César

Other Decks in Technology

Transcript

  1. M A K I N G YO U R L

    I F E E A S I E R W IT H C U S TO M V I E W S YG O R C É S A R
  2. W H AT A R E V I E W

    ? “(View) This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling.  —  Oficial Android documentation”
  3. W I D G E T S / V I

    E W G R O U P V I E W EditText Button TextView ViewGroup RelativeLayout LinearLayout FrameLayout
  4. W H AT A R E C U S TO

    M V I E W S ?
  5. W H E N U S E C U S

    TO M V I E W S ?
  6. S O M E B E N E F IT

    S • Simplify your code • Make your code reusable • Encapsulation • Performance
  7. C O N S T R U CTO R Every

    view starts it’ s life from a Constructor. And what it gives us, is a great opportunity to prepare it for an initial drawing, make various calculation, set default values or whatever we need.
  8. AT T R I B U T E S E

    T Make our view easy to use and setup
  9. AT T R I B U T E S E

    T Create a new file in res/values and call it attrs.xml
  10. I M P L E M E NT I N

    G A S E A R C H V I E W
  11. S E A R C H V I E W

    class SearchView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RelativeLayout(context, attrs, defStyleAttr) Create a new class extends from a View
  12. S E A R C H V I E W

    <declare-styleable name="SearchView"> <attr name="sv_hint" format="reference"/> <attr name="sv_close_icon" format="reference"/> <attr name="sv_close_icon_color" format="color"/> <attr name="sv_field_focusable" format="boolean"/> <attr name="sv_close_button_visibility" format="enum"> <enum name="visible" value="0"/> <enum name="gone" value="1"/> </attr> </declare-styleable> Create a new declare-styleable on res/value/attrs.xml
  13. S E A R C H V I E W

    init { val appearance = context.obtainStyledAttributes( attrs, R.styleable.SearchView ) // . . . } Obtain styled attributes
  14. S E A R C H V I E W

    init { . . . val hintText = appearance.getResourceId( R.styleable.SearchView_sv_hint, R.string.search_hint ) val closeIconColor = appearance.getColor( R.styleable.SearchView_sv_close_icon_color, context.getColorRes(R.color.colorIconSecondary) ) val fieldIsFocusable = appearance.getBoolean( R.styleable.SearchView_sv_field_focusable, true ) val closeButtonVisibility = appearance.getInt( R.styleable.SearchView_sv_close_button_visibility, 0 ) // . . . } Fetch attributes
  15. S E A R C H V I E W

    init { // . . . View.inflate(context, R.layout.search_view, this) // . . . } Inflate the view
  16. S E A R C H V I E W

    init { // . . . searchCloseButton.apply { setImageResource(closeIconResId) setColorFilter(closeIconColor) isVisible = closeButtonVisibility == VISIBLE } searchFilter.isVisible = filterVisibility == VISIBLE searchField.apply { isFocusable = fieldIsFocusable isFocusableInTouchMode = fieldIsFocusable setHint(hintText) } onClearTextClick(isClearButtonEnabled) // . . . } Configure the components
  17. S E A R C H V I E W

    init { // . . . appearance.recycle() // . . . } And then but not last
  18. S E A R C H V I E W

    <merge android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/searchCloseButton" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:src="@drawable/ic_close_black"/> <androidx.appcompat.widget.AppCompatEditText android:id="@+id/searchField" style="@style/SearchEditText" android:layout_width="match_parent" android:layout_height="wrap_content"/> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/searchClearText" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/ic_clear_text"/> <LinearLayout android:id="@+id/searchFilter" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/searchFilterCount" android:layout_width=“22dp” android:layout_height="22dp" android:background="@drawable/circle"/> <TextView android:id="@+id/searchFilterText" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </merge> The layout * The layout xml is no complete, only for show hierarchy and views
  19. S E A R C H V I E W

    class SearchView @JvmOverloads constructor( . . . ) : RelativeLayout(. . .) { private val textObservable = PublishSubject.create<String>() fun observeTextChange(): Observable<String> { searchField?.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(text: Editable?) { val query = text?.toString() ?: "" onTextChange(query) } }) return textObservable.debounce(1, TimeUnit.SECONDS) .filter { it != INVALID_VALUE } } } Encapsulation - Observing TextChange
  20. S E A R C H V I E W

    class SearchView @JvmOverloads constructor( . . . ) : RelativeLayout(. . .) { fun onTextChange(query: String) { searchClearText?.apply { if (isEnabled) isVisible = query.isNotBlank() } when { query.length >= 3 -> textObservable.onNext(query) query.isBlank() -> textObservable.onNext(“") else -> textObservable.onNext(INVALID_VALUE) } } } Encapsulation - Notify TextChange
  21. S E A R C H V I E W

    class SearchView @JvmOverloads constructor( . . . ) : RelativeLayout(. . .) { fun setFilterCount(count: Int) { if (count > 0) setFilterCount(count.toString()) else setFilterCount("") } private fun setFilterCount(count: String) { searchFilterCount?.apply { text = count isVisible = count.isNotBlank() } } } Encapsulation - set FilterCount
  22. S E A R C H V I E W

    Using my CustomView in layout.xml
  23. S E A R C H V I E W

    Using my CustomView in { Activity, Fragment, View } private fun setupSearchView() { searchView.apply { setOnCloseClick { finish() } setOnFilterClick { NavigationManager.goToFilter(this@SearchActivity) } setFilterCount(viewModel.filters.count) observeTextChange() .subscribe({ query -> viewModel.search(query) }, { Timber.e(it) }) .addToComposite(compositeDisposable) } }
  24. T H A N K S , T H AT

    ’ S A L L F O L K S ! ! ! /ygorcesar @ygorcesar @ygorces4r ygorces4r@gmail