Wednesday, April 25, 2018

Android - Circular image view with loader


Introduction:- Reusable component always increase the speed of development. So,  i'm going to explain a component that displays image in the circular shape that's written in Kotlin.

For display user profile pic we always need a circular image view.  Let's dive in the code snippet.

First of all, you need to add it in your layout file: activity_main
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusable="true"
    android:orientation="vertical"
    >
        <com.demo.CircularImageView
            android:id="@+id/fragment_tv_user_pic"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="22dp"
            app:civ_border_color="@color/colorWhite"
            app:civ_border_width="2dp"
            app:matProg_barColor="@color/colorAccent"
            app:matProg_barWidth="2dp"
            app:matProg_circleRadius="60dp"
            app:matProg_progressIndeterminate="true"
            />
   </LinearLayout>


Example-1:

If you want display short form of the name in this component. Use the following code snippet.
 val civUserPic = findViewById<CircularImageView>(R.id.fragment_tv_user_pic)
 civUserPic.createTextPaint("#2CB044", 60, true)
 civUserPic.setText("S.K")
Output:





Example-2:

If you want to display image from res folder in this component. Use the following code snippet.
  civUserPic.setImageDrawable(ContextCompat.getDrawable(this,R.drawable.images))
  civUserPic.setBorderColor(R.color.colorPrimary)

Output:



Example-3:

If you want to display image from the server and you have full path of image use following steps. You can use any  image loader lib like here i'm using Picasso
       civUserPic.loaderState(true)
       civUserPic.invalidate()
       Picasso.with(this).load("image_url").into(civUserPic)

Output:




So, time to view component.

values/attrs:-


<?xml version="1.0" encoding="utf-8"?>
<resources>

     <declare-styleable name="ProgressWheel">
        <attr name="matProg_progressIndeterminate" format="boolean"/>
        <attr name="matProg_barColor" format="color"/>
        <attr name="matProg_rimColor" format="color"/>
        <attr name="matProg_rimWidth" format="dimension"/>
        <attr name="matProg_spinSpeed" format="float"/>
        <attr name="matProg_barSpinCycleTime" format="integer"/>
        <attr name="matProg_circleRadius" format="dimension"/>
        <attr name="matProg_fillRadius" format="boolean"/>
        <attr name="matProg_barWidth" format="dimension"/>
        <attr name="matProg_linearProgress" format="boolean"/>
        <attr name="matProg_visible" format="boolean"/>
     </declare-styleable>

    <declare-styleable name="CircularImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fad_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
    </declare-styleable>

</resources>



CircularImageView.Kt:-
class CircularImageView : AppCompatImageView {
    private val mDrawableRect = RectF()
    private val mBorderRect = RectF()
    private val mShaderMatrix = Matrix()
    private val mBitmapPaint = Paint()
    private val mBorderPaint = Paint()
    private val mFillPaint = Paint()
    private var mBorderColor = DEFAULT_BORDER_COLOR
    private var mBorderWidth = DEFAULT_BORDER_WIDTH
    private var mFillColor = DEFAULT_FILL_COLOR
    private var mBitmap: Bitmap? = null
    private var mBitmapShader: BitmapShader? = null
    private var mBitmapWidth: Int = 0
    private var mBitmapHeight: Int = 0
    private var mDrawableRadius: Float = 0.toFloat()
    private var mBorderRadius: Float = 0.toFloat()
    private var mColorFilter: ColorFilter? = null
    private var mReady: Boolean = false
    private var mSetupPending: Boolean = false
    private var mBorderOverlay: Boolean = false
    private val barLength = 16
    //Sizes (with defaults in DP)
    private var circleRadius = 28
    private var barWidth = 4
    private var rimWidth = 4
    private var fillRadius = false
    private var timeStartGrowing = 0.0
    private var barSpinCycleTime = 460.0
    private var barExtraLength = 0f
    private var barGrowingFromFront = true
    private var pausedTimeWithoutGrowing: Long = 0
    //Colors (with defaults)
    private var barColor = -0x56000000
    private var rimColor = 0x00FFFFFF
    //Paints
    private val barPaint = Paint()
    private val rimPaint = Paint()
    //Rectangles
    private var circleBounds = RectF()
    //Animation
    //The amount of degrees per second
    private var spinSpeed = 230.0f
    // The last time the spinner was animated
    private var lastTimeAnimated: Long = 0
    private var linearProgress: Boolean = false
    var isProgressVisible: Boolean = false
    private var mProgress = 0.0f
    private var mTargetProgress = 0.0f
    private var isSpinning = false
    private var callback: ProgressCallback? = null
    private var mFadEnable: Boolean=false
    private var fadPaint: Paint? = null
    private var fad: Int = 0
    private var textPaint: Paint? = null
    private var text: String? = null
    private var isBorderRequired: Boolean = false
  
    constructor(context: Context) : super(context) {
        init()
    }
  
    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {
        parseAttributes(context.obtainStyledAttributes(attrs,
                R.styleable.ProgressWheel))
    }
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        val a = context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyle, 0)
        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircularImageView_civ_border_width, DEFAULT_BORDER_WIDTH)
        mBorderColor = a.getColor(R.styleable.CircularImageView_civ_border_color, DEFAULT_BORDER_COLOR)
        mBorderOverlay = a.getBoolean(R.styleable.CircularImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)
        mFadEnable = a.getBoolean(R.styleable.CircularImageView_civ_fad_overlay, DEFAULT_BORDER_OVERLAY)
        mFillColor = a.getColor(R.styleable.CircularImageView_civ_fill_color, DEFAULT_FILL_COLOR)
        a.recycle()
        init()
    }
  
    private fun init() {
        super.setScaleType(SCALE_TYPE)
        mReady = true
        if (mSetupPending) {
            setup()
            mSetupPending = false
        }
    }
    override fun getScaleType(): ImageView.ScaleType {
        return SCALE_TYPE
    }
    override fun setScaleType(scaleType: ImageView.ScaleType) {
        if (scaleType != SCALE_TYPE) {
            throw IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType))
        }
    }
    override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
        if (adjustViewBounds) {
            throw IllegalArgumentException("adjustViewBounds not supported.")
        }
    }
    override fun onDraw(canvas: Canvas) {
        if (mBitmap != null) {
            //draw image
            if (mFillColor != Color.TRANSPARENT) {
                canvas.drawCircle(width / 2.0f, height / 2.0f, mDrawableRadius, mFillPaint)
            }
            canvas.drawCircle(width / 2.0f, height / 2.0f, mDrawableRadius, mBitmapPaint)
            if (mBorderWidth != 0) {
                canvas.drawCircle(width / 2.0f, height / 2.0f, mBorderRadius, mBorderPaint)
            }
            isProgressVisible = false
        } else if (text != null && !text!!.isEmpty() && textPaint != null) {
            if (isBorderRequired && mBorderWidth != 0) {
                canvas.drawCircle(width / 2.0f, height / 2.0f, mBorderRadius, mBorderPaint)
            }
            canvas.drawText(text!!, width / 2.0f - textPaint!!.measureText(text) / 2, height / 2.0f - (textPaint!!.descent() + textPaint!!.ascent()) / 2, textPaint!!)
        }
        if (mFadEnable && fadPaint != null) {
            fadPaint!!.alpha = fad
            canvas.drawCircle(width / 2.0f, height / 2.0f, mDrawableRadius, fadPaint!!)
        }
        //draw progress bar
        if (isProgressVisible) {
            drawProgressBar(canvas)
        }
    }
    fun loaderState(state: Boolean) {
        isProgressVisible = state
    }
    private fun drawProgressBar(canvas: Canvas) {
        canvas.drawArc(circleBounds, 360f, 360f, false, rimPaint)
        var mustInvalidate = false
        if (isSpinning) {
            //Draw the spinning bar
            mustInvalidate = true
            val deltaTime = SystemClock.uptimeMillis() - lastTimeAnimated
            val deltaNormalized = deltaTime * spinSpeed / 1000.0f
            updateBarLength(deltaTime)
            mProgress += deltaNormalized
            if (mProgress > 360) {
                mProgress -= 360f
                // A full turn has been completed
                // we run the callback with -1 in case we want to
                // do something, like changing the color
                runCallback(-1.0f)
            }
            lastTimeAnimated = SystemClock.uptimeMillis()
            var from = mProgress - 90
            var length = barLength + barExtraLength
            if (isInEditMode) {
                from = 0f
                length = 135f
            }
            canvas.drawArc(circleBounds, from, length, false,
                    barPaint)
        } else {
            val oldProgress = mProgress
            if (mProgress != mTargetProgress) {
                //We smoothly increase the progress bar
                mustInvalidate = true
                val deltaTime = (SystemClock.uptimeMillis() - lastTimeAnimated).toFloat() / 1000
                val deltaNormalized = deltaTime * spinSpeed
                mProgress = Math.min(mProgress + deltaNormalized, mTargetProgress)
                lastTimeAnimated = SystemClock.uptimeMillis()
            }
            if (oldProgress != mProgress) {
                runCallback()
            }
            var offset = 0.0f
            var progress = mProgress
            if (!linearProgress) {
                val factor = 2.0f
                offset = (1.0f - Math.pow((1.0f - mProgress / 360.0f).toDouble(), (2.0f * factor).toDouble())).toFloat() * 360.0f
                progress = (1.0f - Math.pow((1.0f - mProgress / 360.0f).toDouble(), factor.toDouble())).toFloat() * 360.0f
            }
            if (isInEditMode) {
                progress = 360f
            }
            canvas.drawArc(circleBounds, offset - 90, progress, false, barPaint)
        }
        if (mustInvalidate) {
            invalidate()
        }
    }
    override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
        super.onSizeChanged(w, h, oldW, oldH)
        setUpBounds(w, h)
        setupPaints()
        setup()
    }
    override fun setImageBitmap(bm: Bitmap) {
        super.setImageBitmap(bm)
        mBitmap = bm
        setup()
    }
    override fun setImageDrawable(drawable: Drawable?) {
        super.setImageDrawable(drawable)
        mBitmap = getBitmapFromDrawable(drawable)
        setup()
    }
    override fun setImageResource(@DrawableRes resId: Int) {
        super.setImageResource(resId)
        mBitmap = getBitmapFromDrawable(drawable)
        setup()
    }
    override fun setImageURI(uri: Uri?) {
        super.setImageURI(uri)
        mBitmap = if (uri != null) getBitmapFromDrawable(drawable) else null
        setup()
    }
    override fun setColorFilter(cf: ColorFilter) {
        if (cf === mColorFilter) {
            return
        }
        mColorFilter = cf
        mBitmapPaint.colorFilter = mColorFilter
        invalidate()
    }
   
    private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
        if (drawable == null) {
            return null
        }
        if (drawable is BitmapDrawable) {
            return drawable.bitmap
        }
        try {
            val bitmap: Bitmap
            if (drawable is ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG)
            } else {
                bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, BITMAP_CONFIG)
            }
            val canvas = Canvas(bitmap)
            drawable.setBounds(0, 0, canvas.width, canvas.height)
            drawable.draw(canvas)
            return bitmap
        } catch (e: Exception) {
            Log.d("error", e.toString())
            return null
        }
    }
    private fun setup() {
        if (!mReady) {
            mSetupPending = true
            return
        }
        if (width == 0 && height == 0) {
            return
        }
        mBorderPaint.style = Paint.Style.STROKE
        mBorderPaint.isAntiAlias = true
        mBorderPaint.color = mBorderColor
        mBorderPaint.strokeWidth = mBorderWidth.toFloat()
        mFillPaint.style = Paint.Style.FILL
        mFillPaint.isAntiAlias = true
        mFillPaint.color = mFillColor
        mBorderRect.set(0f, 0f, width.toFloat(), height.toFloat())
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f)
        mDrawableRect.set(mBorderRect)
        if (!mBorderOverlay) {
            mDrawableRect.inset(mBorderWidth.toFloat(), mBorderWidth.toFloat())
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f)
        if (mBitmap == null) {
            invalidate()
            return
        }
        mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
        mBitmapPaint.isAntiAlias = true
        mBitmapPaint.shader = mBitmapShader
        mBitmapHeight = mBitmap!!.height
        mBitmapWidth = mBitmap!!.width
        updateShaderMatrix()
        invalidate()
    }
    fun setBorderColor(color: Int) {
        mBorderColor = color
        mBorderPaint.color = mBorderColor
        invalidate()
    }
    private fun updateShaderMatrix() {
        val scale: Float
        var dx = 0f
        var dy = 0f
        mShaderMatrix.set(null)
        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / mBitmapHeight.toFloat()
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f
        } else {
            scale = mDrawableRect.width() / mBitmapWidth.toFloat()
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f
        }
        mShaderMatrix.setScale(scale, scale)
        mShaderMatrix.postTranslate((dx + 0.5f).toInt() + mDrawableRect.left, (dy + 0.5f).toInt() + mDrawableRect.top)
        mBitmapShader!!.setLocalMatrix(mShaderMatrix)
    }
    fun createFadPaint(color: String) {
        fadPaint = Paint()
        fadPaint!!.style = Paint.Style.FILL
        fadPaint!!.isAntiAlias = true
        fadPaint!!.color = Color.parseColor(color)
    }
    fun createTextPaint(color: String, fontSize: Int, isBorderRequired: Boolean) {
        textPaint = Paint()
        textPaint!!.style = Paint.Style.FILL
        textPaint!!.isAntiAlias = true
        textPaint!!.color = Color.parseColor(color)
        textPaint!!.textSize = fontSize.toFloat()
        this.isBorderRequired = isBorderRequired
    }
    fun setText(text: String) {
        this.text = text
        mBitmap = null
        Log.i(">>>", "" + text)
        invalidate()
    }
    fun setFad(fad: Int) {
        this.fad = fad
        Log.i(">>>", "" + fad)
        invalidate()
    }
  
    interface ProgressCallback {
    
        fun onProgressUpdate(progress: Float)
    }
  
    private fun parseAttributes(a: TypedArray) {
        // We transform the default values from DIP to pixels
        val metrics = context.resources.displayMetrics
        barWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, barWidth.toFloat(), metrics).toInt()
        rimWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rimWidth.toFloat(), metrics).toInt()
        circleRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleRadius.toFloat(), metrics).toInt()
        circleRadius = a.getDimension(R.styleable.ProgressWheel_matProg_circleRadius, circleRadius.toFloat()).toInt()
        fillRadius = a.getBoolean(R.styleable.ProgressWheel_matProg_fillRadius, false)
        barWidth = a.getDimension(R.styleable.ProgressWheel_matProg_barWidth, barWidth.toFloat()).toInt()
        rimWidth = a.getDimension(R.styleable.ProgressWheel_matProg_rimWidth, rimWidth.toFloat()).toInt()
        val baseSpinSpeed = a.getFloat(R.styleable.ProgressWheel_matProg_spinSpeed, spinSpeed / 360.0f)
        spinSpeed = baseSpinSpeed * 360
        barSpinCycleTime = a.getInt(R.styleable.ProgressWheel_matProg_barSpinCycleTime, barSpinCycleTime.toInt()).toDouble()
        barColor = a.getColor(R.styleable.ProgressWheel_matProg_barColor, barColor)
        rimColor = a.getColor(R.styleable.ProgressWheel_matProg_rimColor, rimColor)
        isProgressVisible = a.getBoolean(R.styleable.ProgressWheel_matProg_visible, false)
        linearProgress = a.getBoolean(R.styleable.ProgressWheel_matProg_linearProgress, false)
        if (a.getBoolean(R.styleable.ProgressWheel_matProg_progressIndeterminate, false)) {
            spin()
        }
        // Recycle
        a.recycle()
    }
    private fun spin() {
        lastTimeAnimated = SystemClock.uptimeMillis()
        isSpinning = true
        invalidate()
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val viewWidth = circleRadius + this.paddingLeft + this.paddingRight
        val viewHeight = circleRadius + this.paddingTop + this.paddingBottom
        val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
        val width: Int
        val height: Int
        //Measure Width
        if (widthMode == View.MeasureSpec.EXACTLY) {
            //Must be this size
            width = widthSize
        } else if (widthMode == View.MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            width = Math.min(viewWidth, widthSize)
        } else {
            //Be whatever you want
            width = viewWidth
        }
        //Measure Height
        if (heightMode == View.MeasureSpec.EXACTLY || widthMode == View.MeasureSpec.EXACTLY) {
            //Must be this size
            height = heightSize
        } else if (heightMode == View.MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            height = Math.min(viewHeight, heightSize)
        } else {
            //Be whatever you want
            height = viewHeight
        }
        setMeasuredDimension(width, height)
    }
 private fun setUpBounds(layoutWidth: Int, layoutHeight: Int) {
        val paddingTop = paddingTop
        val paddingBottom = paddingBottom
        val paddingLeft = paddingLeft
        val paddingRight = paddingRight
        if (!fillRadius) {
            // Width should equal to Height, find the min value to setup the circle
            val minValue = Math.min(layoutWidth - paddingLeft - paddingRight,
                    layoutHeight - paddingBottom - paddingTop)
            val circleDiameter = Math.min(minValue, circleRadius * 2 - barWidth * 2)
            // Calc the Offset if needed for centering the wheel in the available space
            val xOffset = (layoutWidth - paddingLeft - paddingRight - circleDiameter) / 2 + paddingLeft
            val yOffset = (layoutHeight - paddingTop - paddingBottom - circleDiameter) / 2 + paddingTop
            circleBounds = RectF((xOffset + barWidth).toFloat(),
                    (yOffset + barWidth).toFloat(),
                    (xOffset + circleDiameter - barWidth).toFloat(),
                    (yOffset + circleDiameter - barWidth).toFloat())
        } else {
            circleBounds = RectF((paddingLeft + barWidth).toFloat(),
                    (paddingTop + barWidth).toFloat(),
                    (layoutWidth - paddingRight - barWidth).toFloat(),
                    (layoutHeight - paddingBottom - barWidth).toFloat())
        }
    }
  
 private fun setupPaints() {
        barPaint.color = barColor
        barPaint.isAntiAlias = true
        barPaint.style = Paint.Style.STROKE
        barPaint.strokeWidth = barWidth.toFloat()
        rimPaint.color = rimColor
        rimPaint.isAntiAlias = true
        rimPaint.style = Paint.Style.STROKE
        rimPaint.strokeWidth = rimWidth.toFloat()
    }
  fun setCallback(progressCallback: ProgressCallback) {
        callback = progressCallback
        if (!isSpinning) {
            runCallback()
        }
    }
    private fun runCallback() {
        if (callback != null) {
            val normalizedProgress = Math.round(mProgress * 100 / 360.0f).toFloat() / 100
            callback!!.onProgressUpdate(normalizedProgress)
        }
    }
    private fun runCallback(value: Float) {
        if (callback != null) {
            callback!!.onProgressUpdate(value)
        }
    }
    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
        if (visibility == View.VISIBLE) {
            lastTimeAnimated = SystemClock.uptimeMillis()
        }
    }
    private fun updateBarLength(deltaTimeInMilliSeconds: Long) {
        if (pausedTimeWithoutGrowing >= 200) {
            timeStartGrowing += deltaTimeInMilliSeconds.toDouble()
            if (timeStartGrowing > barSpinCycleTime) {
                // We completed a size change cycle
                // (growing or shrinking)
                timeStartGrowing -= barSpinCycleTime
                pausedTimeWithoutGrowing = 0
                barGrowingFromFront = !barGrowingFromFront
            }
            val distance = Math.cos((timeStartGrowing / barSpinCycleTime + 1) * Math.PI).toFloat() / 2 + 0.5f
            val destLength = 270.toFloat() - barLength
            if (barGrowingFromFront) {
                barExtraLength = distance * destLength
            } else {
                val newLength = destLength * (1 - distance)
                mProgress += barExtraLength - newLength
                barExtraLength = newLength
            }
        } else {
            pausedTimeWithoutGrowing += deltaTimeInMilliSeconds
        }
    }
  
    fun stopSpinning() {
        isSpinning = false
        mProgress = 0.0f
        mTargetProgress = 0.0f
        invalidate()
    }
    public override fun onSaveInstanceState(): Parcelable? {
        val superState = super.onSaveInstanceState()
        val ss = WheelSavedState(superState)
        // We save everything that can be changed at runtime
        ss.mProgress = this.mProgress
        ss.mTargetProgress = this.mTargetProgress
        ss.isSpinning = this.isSpinning
        ss.spinSpeed = this.spinSpeed
        ss.barWidth = this.barWidth
        ss.barColor = this.barColor
        ss.rimWidth = this.rimWidth
        ss.rimColor = this.rimColor
        ss.circleRadius = this.circleRadius
        ss.linearProgress = this.linearProgress
        ss.fillRadius = this.fillRadius
        return ss
    }
    public override fun onRestoreInstanceState(state: Parcelable) {
        if (state !is WheelSavedState) {
            super.onRestoreInstanceState(state)
            return
        }
        super.onRestoreInstanceState(state.superState)
        this.mProgress = state.mProgress
        this.mTargetProgress = state.mTargetProgress
        this.isSpinning = state.isSpinning
        this.spinSpeed = state.spinSpeed
        this.barWidth = state.barWidth
        this.barColor = state.barColor
        this.rimWidth = state.rimWidth
        this.rimColor = state.rimColor
        this.circleRadius = state.circleRadius
        this.linearProgress = state.linearProgress
        this.fillRadius = state.fillRadius
        this.lastTimeAnimated = SystemClock.uptimeMillis()
    }
   
    internal class WheelSavedState : View.BaseSavedState {
      
        var mProgress: Float = 0.toFloat()
        
        var mTargetProgress: Float = 0.toFloat()
      
        var isSpinning: Boolean = false
      
        var spinSpeed: Float = 0.toFloat()
      
        var barWidth: Int = 0
        
        var barColor: Int = 0
        
        var rimWidth: Int = 0
      
        var rimColor: Int = 0
        
        var circleRadius: Int = 0
       
        var linearProgress: Boolean = false
       
        var fillRadius: Boolean = false
        constructor(superState: Parcelable) : super(superState) {}
        private constructor(`in`: Parcel) : super(`in`) {
            this.mProgress = `in`.readFloat()
            this.mTargetProgress = `in`.readFloat()
            this.isSpinning = `in`.readByte().toInt() != 0
            this.spinSpeed = `in`.readFloat()
            this.barWidth = `in`.readInt()
            this.barColor = `in`.readInt()
            this.rimWidth = `in`.readInt()
            this.rimColor = `in`.readInt()
            this.circleRadius = `in`.readInt()
            this.linearProgress = `in`.readByte().toInt() != 0
            this.fillRadius = `in`.readByte().toInt() != 0
        }
        override fun writeToParcel(out: Parcel, flags: Int) {
            super.writeToParcel(out, flags)
            out.writeFloat(this.mProgress)
            out.writeFloat(this.mTargetProgress)
            out.writeByte((if (isSpinning) 1 else 0).toByte())
            out.writeFloat(this.spinSpeed)
            out.writeInt(this.barWidth)
            out.writeInt(this.barColor)
            out.writeInt(this.rimWidth)
            out.writeInt(this.rimColor)
            out.writeInt(this.circleRadius)
            out.writeByte((if (linearProgress) 1 else 0).toByte())
            out.writeByte((if (fillRadius) 1 else 0).toByte())
        }
        companion object {
          
            //required field that makes Parcelables from a Parcel
            val CREATOR: Parcelable.Creator<WheelSavedState> = object : Parcelable.Creator<WheelSavedState> {
                override fun createFromParcel(`in`: Parcel): WheelSavedState {
                    return WheelSavedState(`in`)
                }
                override fun newArray(size: Int): Array<WheelSavedState?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }
    companion object {
        private val SCALE_TYPE = ImageView.ScaleType.CENTER_CROP
        private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
        private val COLOR_DRAWABLE_DIMENSION = 2
        private val DEFAULT_BORDER_WIDTH = 0
        private val DEFAULT_BORDER_COLOR = Color.BLACK
        private val DEFAULT_FILL_COLOR = Color.TRANSPARENT
        private val DEFAULT_BORDER_OVERLAY = false
    }
}



Let me know if you facing issue to use it.



Share:

Sunday, April 15, 2018

Android - Native Development Kit (NDK)

Introduction: As we know, Java is the default programming language to make an Android application. But these languages are not the best solution for some time. Like if you want to make games. Where we need fast calculation. Today, a lot of gaming engines are working with c/c++. To achieve it in Android Google offers the Android Native Development Kit (NDK).

What is the NDK?

The NDK allows developers to write optimized code in C/C++ that compiles to native code. With the NDK, you can make games with better performance on Android devices. We have the following reason to use NDK to make game.

1. Memory management and performance.
2. You can use Renderscript
3. C/C++ have a large body of existing legacy code.
4. We can develop a multiplatform application, the NDK is unbeatable in this domain. Since the same code written in C + + for Android can be easily ported and run the same way on the iOS, Windows or any other platform without changing the original code.

But in some cases, NDK is not recommended, Because.

1. Harder to debug
2. Reduce flexibility.

How it works?

As we know everything in an Android application is execute in the context of the Dalvik Virtual Machine or ART. In order to integrate your C/C++ code with this environment. We have to use the JNI (Java Native Interface). This is how the Java and C/C++ components talk to each other.

For use it in Android, We have to call System.loadLibrary("Name of file") static method from Java. When you load a .so file, its JNI_OnLoad() function is called if it exists and functions starting with Java_ are mapped to the corresponding Java methods. These methods are to be declared in Java classes and have the "keyword" native to specify that their implementation comes from a .so file.
public native int numbers(int x, int y);

At runtime, when the Java execution reaches a method with its implementation being native. This native implementation is directly executed. When it returns, the Java code execution continues.

The JNI also gives you jni.h header, that gives you the methods to access the Java environment (JavaVM*, JNIEnv*) so you can manipulate, create and access Java primitives (jint, jlong.), objects (jobject, jclass.), exceptions (jthrowable), etc.

NDK setup with Android studio

Android Studio only includes the default tools and SDK. So, you can download and install the NDK package separately.






Example


So, Let create your first JNI application written in C/C++ with Android Studio.




Once you have done all the steps. You will see cpp folder that contains all C/C++ files.




The “ndk-build” script is responsible for automatically synch with the project and determining what to build. The script is also responsible for generating binaries and copying those binaries to your app’s project path.

There are also native shared libraries (.so) that the NDK builds from the native source code and Native static libraries (.a), and these can be linked against other libraries. The Application Binary Interface (ABI) uses the .so files to know exactly how your app’s machine code will work with the system when the app is running. The NDK supports ARMABI by default along with MIPS and x86.

Here, We have java file that load and map with C/C+ methods.
public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(native_say_hello_world());
    }
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    private native String native_say_hello_world();
}

On the side c++ side, We have created a cpp file and a method that refers to the native method with that was created during step #2. The method has to be named correctly, including the name of the Java package and class. This method can have parameters and returns Java classes such as jobject, jstring, jint or whatever that starts with the letter j. We can return void as well.
JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_native_say_hello_world(JNIEnv* env, jobject thiz ) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}




Share:

Wednesday, April 11, 2018

Android - Google map cluster utility

Introduction:  Google map is a great way to show the user current location in the world and some time we need to show more than one image at a location. The cluster is nothing more than a group of markers combined together.

To achieve it in Android google provide us dependencies that we have to add in the application grade file.
compile 'com.google.android.gms:play-services-maps:9.0.2'
compile 'com.google.maps.android:android-maps-utils:0.4.3'

Here, i'm going to explain a clustering utility that i have used in one of my projects.  So, for use it you have to provide an instance of google map and screen context in the constructor of utility and call init() method for initial setup. And call addUser() method with locations of the item.
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.DefaultClusterRenderer;
import com.google.maps.android.ui.IconGenerator;
import java.util.List;
public class GoogleMapClusterUtil implements ClusterManager.OnClusterClickListener <GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterInfoWindowClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemInfoWindowClickListener<GoogleMapClusterUtil.Person> {
    private final Context mContext;
    private final GoogleMap mGoogleMap;
    private ClusterManager<Person> mClusterManager;
    public GoogleMapClusterUtil(Context context, GoogleMap googleMap) {
        mContext = context;
        mGoogleMap = googleMap;
    }
    /**
     * Create instance of cluster.
     */
    public void init() {
        mClusterManager = new ClusterManager<>(mContext, mGoogleMap);
        mClusterManager.setRenderer(new PersonRenderer());
        mGoogleMap.setOnMarkerClickListener(mClusterManager);
        mGoogleMap.setOnInfoWindowClickListener(mClusterManager);
        mClusterManager.setOnClusterClickListener(this);
        mClusterManager.setOnClusterInfoWindowClickListener(this);
        mClusterManager.setOnClusterItemClickListener(this);
        mClusterManager.setOnClusterItemInfoWindowClickListener(this);
        mClusterManager.cluster();
}
 
    /**
     * Update cluster on camera idle.
     */
    public void onCameraIdle() {
        mClusterManager.onCameraIdle();
    }
    public void addUser(List<Location> userNearMe) {
        clearAllItem();
        for (Location appUserLocation : userNearMe) {
            // the address is blank for now, will be used later
            mClusterManager.addItem(new Person(new LatLng(appUserLocation.getLatitude(), appUserLocation.getLongitude()), 0));
        }
    }
  
    /**
     * Delete all cluster from map.
     */
    public void clearAllItem() {
        mClusterManager.clearItems();
    }
    @Override
    public boolean onClusterClick(Cluster<Person> cluster) {
      
        // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
        // inside of bounds, then animate to center of the bounds.
        // Create the builder to collect all essential cluster items for the bounds.
        LatLngBounds.Builder builder = LatLngBounds.builder();
        for (ClusterItem item : cluster.getItems()) {
            builder.include(item.getPosition());
        }
        // Get the LatLngBounds
        final LatLngBounds bounds = builder.build();
        // Animate camera to the bounds
        try {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
        } catch (Exception e) {
            Crashlytics.logException(e);
        }
        return true;
    }
    @Override
    public void onClusterInfoWindowClick(Cluster<Person> cluster) {
        // onClusterInfoWindowClick
    }
    @Override
    public boolean onClusterItemClick(Person person) {
     
        return false;
    }
    @Override
    public void onClusterItemInfoWindowClick(Person person) {
        // onClusterItemInfoWindowClick
    }
   
    public class Person implements ClusterItem {
        public final int id;
        private final LatLng mPosition;
        private Person(LatLng position, int id) {
            this.id = id;
            mPosition = position;
        }
        public int getId() {
            return id;
        }
        @Override
        public LatLng getPosition() {
            return mPosition;
        }
        @Override
        public String getTitle() {
            return null;
        }
        @Override
        public String getSnippet() {
            return null;
        }
    }
    /**
     * Manage cluster update.
     */
    private class PersonRenderer extends DefaultClusterRenderer<Person> {
        private PersonRenderer() {
            super(mContext, mGoogleMap, mClusterManager);
        }
        @Override
        protected void onBeforeClusterItemRendered(Person person, MarkerOptions markerOptions) {
            // Draw a single person.
            // Set the info window to show their name.
            if (person.getId()>0){
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location_package));
            }else{
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location));
            }
        }
        @Override
        protected void onBeforeClusterRendered(Cluster<Person> cluster, MarkerOptions markerOptions) {
            // Draw multiple people.
            // Note: this method runs on the UI thread. Don't spend too much time in here (like in this example).
            IconGenerator iconGenerator = new IconGenerator(mContext);
            iconGenerator.setBackground(null);
            LayoutInflater myInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View activityView = myInflater.inflate(R.layout.item_cluster, null, false);
            activityView.findViewById(R.id.item_cluster_iv_cluster_icon).setBackgroundResource(R.drawable.location_blank);
            if (cluster.getSize() > 99) {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf("99+"));
            } else {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf(cluster.getSize()));
            }
            iconGenerator.setContentView(activityView);
            BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon());
            markerOptions.icon(icon);
        }
        @Override
        protected boolean shouldRenderAsCluster(Cluster cluster) {
            // Always render clusters.
            return cluster.getSize() > 1;
        }
    }
}


for display cluster google map i have used following XML file. You can change it according to your requirement.
layout: item_cluster
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="30dp"
             android:layout_height="30dp"
             android:padding="2dp">
    <ImageView
        android:id="@+id/item_cluster_iv_cluster_icon"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@null"/>
    <TextView
        android:id="@+id/amu_text"
        android:layout_width="wrap_parent"
        android:layout_height="wrap_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:paddingBottom="2dp"
        />
</FrameLayout>



At the end build it in your project. You will see, after double click on the map. it will expand item.


 




Share:

Sunday, March 4, 2018

Firebase - Introduction of Cloud Functions

Back-end of any application is a heart that performs all task for the end user. Back-end executes tasks like: send push notifications, email and update other user information. It means that you no longer have to deploy your own server. You need only deploy your functions and all done. That's great for a mobile developer.
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...