• Wednesday, March 13, 2019

    Fan Controller Kotlin - Create a custom view from scratch (Advanced Android 10.1 Part B)

    Here we discuss about creating a fan controller from scratch by using Kotlin and AndroidX.

    Fan Control view at initial stage

    Fan control view when user tap on it.


    For creating Fan Controller, a custom view from scratch, you need the following steps:

    STEP 1:
    Create a new Android Project "Fan Controller"

    STEP 2:
    Goto "layout_main.xml" and a ImageView and a TextView like this:

            
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Fan Control"
                android:id="@+id/customViewLabel"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Display1"
                android:padding="16dp"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginTop="24dp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>
    
        <ImageView
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:id="@+id/dialView" android:layout_marginTop="8dp"
                app:layout_constraintTop_toBottomOf="@+id/customViewLabel" app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
                android:background="@android:color/darker_gray"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
            
        




    STEP 3:

    Create a new kotlin class DialView which will extends View like this:

    
    class DialView: android.view.View {
        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    }
    
    


    The SELECTION_COUNT defines the total number of selections for this custom view. The code is designed so that you can change this value to create a control with more or fewer selections.

    The mTempLabel and mTempResult member variables provide temporary storage for the result of calculations, and are used to reduce the memory allocations while drawing.


    Add the following static object and class fields

            
        companion object {
            private var SELECTION_COUNT = 4
        }
        
        private var mWidth = 0f
        private var mHeight = 0f
        private var mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = Color.BLACK
            style = Paint.Style.FILL_AND_STROKE
            textAlign = Paint.Align.CENTER
            textSize = 40f
        }
        private var mDialPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = Color.GRAY
        }
        private var mRadius = 0f
        private var mActiveSelection = 0
    
        private val mTempLabel = StringBuffer(8)
        private val mTempResult = FloatArray(2)
            
        




    Because a custom view extends View, you can override View methods such as onSizeChanged() to control its behavior. In this case you want to determine the drawing bounds for the custom view's dial by setting its width and height, and calculating its radius, when the view size changes, which includes the first time it is drawn. Add the following to DialView

    Now override onSizeChanged method  and get value for width and height

            
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            mWidth = w.toFloat()
            mHeight = h.toFloat()
            mRadius = (Math.min(mWidth,mHeight)/2.0 * 0.8).toFloat()
        }
            
        


    STEP 4: DRAW THE CUSTOM VIEW


    To draw the custom view, your code needs to render an outer grey circle to serve as the dial, and a smaller black circle to serve as the indicator. The position of the indicator is based on the user's selection captured in mActiveSelection. Your code must calculate the indicator position before rendering the view. After adding the code to calculate the position, override the onDraw() method to render the view.

    The code for drawing this view is provided without explanation because the focus of this lesson is creating and using a custom view. The code uses the Canvas methods drawCircle() and drawText()


    Add the following computeXYForPosition() method to DialView to compute the X and Y coordinates for the text label and indicator (0, 1, 2, or 3) of the chosen selection, given the position number and radius:

            
        private fun computeXYForPosition(position:Int, radius:Float):FloatArray{
            val result = mTempResult
            val startAngle = Math.PI * (9 / 8.0)
            val angle = startAngle + (position * (Math.PI / 4.0))
            result[0] = (radius * Math.cos(angle) + (mWidth/2.0)).toFloat()
            result[1] = (radius * Math.sin(angle) + (mHeight/2.0)).toFloat()
            return result
        }
            
        

    The position parameter is a position index (starting at 0). The radius parameter is for the outer circle.

    You will use the computeXYForPosition() method in the onDraw() method. It returns a two-element array for the position, in which element 0 is the X coordinate, and element 1 is the Y coordinate.

    To render the view on the screen, use the following code to override the onDraw() method for the view. It uses drawCircle() to draw a circle for the dial, and to draw the indicator mark. It uses drawText() to place text for labels, using a StringBuffer for the label text.


            
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            canvas?.let {c->
                c.drawCircle(mWidth/2,mHeight/2,mRadius,mDialPaint)
                val labelRadius = mRadius + 20
                val label = mTempLabel
                for (i in 0 until SELECTION_COUNT){
                    val xyData = computeXYForPosition(i,labelRadius)
                    label.setLength(0)
                    label.append(i)
                    c.drawText(label,0,label.length,xyData[0],xyData[1],mTextPaint)
                }
    
                val markerRadius = mRadius - 35
                val xyData = computeXYForPosition(mActiveSelection,markerRadius)
                canvas.drawCircle(xyData[0],xyData[1],20f,mTextPaint)
            }
        }
            
        


    STEP 5: ADD CUSTOM VIEW TO LAYOUT

    Replace ImageView with your custom view in layout_main.xml, delete the background attribute and leave all attributes as it is.

    Run the app to see changes

    Android Fan Controller View
    Fan Controller View




    STEP 6: ADD CLICK LISTENER FOR CONTROLLING

    To add behavior to the custom view, add an OnClickListener() to the DialView init{} method to perform an action when the user taps the view. Each tap should move the selection indicator to the next position: 0-1-2-3 and back to 0. Also, if the selection is 1 or higher, change the background from gray to green (indicating that the fan power is on):



    Add the init{} method after constructor definition or anywhere you want.

            
        init {
            setOnClickListener {
                mActiveSelection = (mActiveSelection+1) % SELECTION_COUNT
                if (mActiveSelection >= 1){
                    mDialPaint.color = Color.GREEN
                }else{
                    mDialPaint.color = Color.GRAY
                }
    
                invalidate()
            }
        }
            
        


    The invalidate() method of View invalidates the entire view, forcing a call to onDraw() to redraw the view. If something in your custom view changes and the change needs to be displayed, you need to call invalidate().
    now

    Run the app. Tap the DialView element to move the indicator from 0 to 1. The dial should turn green. With each tap, the indicator should move to the next position. When the indicator reaches 0, the dial should turn gray.


    Complete source code will available shortly











    Art Soft

    Author & Editor

    Hi, This is Syed Masoom Rizvi, who is continue exploring the world of programming ,programming and programming... :)

    0 comments:

    Post a Comment