How to add shimmer effect to imageview in android? - android

I need to add Shimmer effect to image view as given in the link for my ImageView. The animation should be from bottom to top instead of left to right as in the sample picture. I have tried the facebook shimmer libraby but it supports from API 16 above only. I need to support if from 14 above. I have also tried this library but it doesn't have support for ImageViews as well as bottom to top animation. Is there any library to achieve the shimmer effect for an Imageview (with bottom to top animation)? or is there any way to implement this feature using ImageView?

You can create your own custom view!
class ShimmerView : View, ValueAnimator.AnimatorUpdateListener{
constructor(context: Context)
: super(context) { init() }
constructor(context: Context, attrs: AttributeSet)
: super(context, attrs) { init() }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) { init() }
companion object {
const val EDGE_ALPHA = 12
const val SHADER_COLOR_R = 170
const val SHADER_COLOR_G = 170
const val SHADER_COLOR_B = 170
const val CENTER_ALPHA = 100
const val ITEM_BG_COLOR = Color.WHITE
val EDGE_COLOR = Color.argb(EDGE_ALPHA, SHADER_COLOR_R, SHADER_COLOR_G, SHADER_COLOR_B)
val CENTER_COLOR = Color.argb(CENTER_ALPHA, SHADER_COLOR_R, SHADER_COLOR_G, SHADER_COLOR_B)
const val LIST_ITEM_LINES = 3
const val CORNER_RADIUS = 2
const val LINE_HEIGHT = 15
const val H_SPACING = 12
const val W_SPACING = 16
const val IMAGE_SIZE = 50
const val ANIMATION_DURATION = 1500L
}
private var listItems: Bitmap? = null
private var animator: ValueAnimator? = null
private var paint: Paint? = null
private var shaderPaint: Paint? = null
private var shaderColors: IntArray? = null
private var lineHeight: Float = 0F
private var hSpacing: Float = 0F
private var wSpacing: Float = 0F
private var imageSize: Float = 0F
private var cornerRadius: Float = 0F
// 1. Инициализируем переменные.
// 1. Initialize variables.
fun init() {
val metric = context.resources.displayMetrics
cornerRadius = dpToPixels(metric, CORNER_RADIUS)
hSpacing = dpToPixels(metric, H_SPACING)
wSpacing = dpToPixels(metric, W_SPACING)
lineHeight = spToPixels(metric, LINE_HEIGHT)
imageSize = dpToPixels(metric, IMAGE_SIZE)
animator = ValueAnimator.ofFloat(-1F, 2F)
animator?.duration = ANIMATION_DURATION
animator?.interpolator = LinearInterpolator()
animator?.repeatCount = ValueAnimator.INFINITE
animator?.addUpdateListener(this)
paint = Paint()
shaderPaint = Paint()
shaderPaint?.isAntiAlias = true
shaderColors = intArrayOf(EDGE_COLOR, CENTER_COLOR, EDGE_COLOR)
}
// 2. Когда View отобразилась на экране, запускаем анимацию.
// 2. When View is displayed on the screen, run the animation.
override fun onVisibilityChanged(changedView: View?, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
when(visibility) {
VISIBLE -> animator?.start()
INVISIBLE, GONE -> animator?.cancel()
}
}
// 3. При выполнении анимации, изменяем положение шейдера и перерисовываем View.
// 3. When the animation, change the position of the shader and redraw View.
override fun onAnimationUpdate(valueAnimator: ValueAnimator) {
if(isAttachedToWindow) {
val factor: Float = valueAnimator.animatedValue as Float
updateShader(width = width.toFloat(), factor = factor)
invalidate()
}
}
// 4. Одновременно со стартом анимации, рисуем элементы.
// 4. Simultaneously with the start of the animation, draw the elements.
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateShader(width = w.toFloat())
if (h > 0 && w > 0) {
drawListItems(w, h)
} else {
listItems = null
animator?.cancel()
}
}
private fun updateShader(width: Float, factor: Float = -1F) {
val left = width * factor
val shader = LinearGradient(
left, 0F, left+width, 0F, shaderColors, floatArrayOf(0f, 0.5f, 1f), Shader.TileMode.CLAMP)
shaderPaint?.shader = shader
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(EDGE_COLOR)
canvas.drawRect(0F, 0F, canvas.width.toFloat(), canvas.height.toFloat(), shaderPaint)
if (listItems != null) { canvas.drawBitmap(listItems, 0F, 0F, paint) }
}
private fun drawListItems(w: Int, h: Int) {
listItems = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val canvas = Canvas(listItems)
val item = getItemBitmap(w)
var top = 0
do {
canvas.drawBitmap(item, 0F, top.toFloat(), paint)
top += item.height
} while (top < canvas.height)
canvas.drawColor(ITEM_BG_COLOR, PorterDuff.Mode.SRC_IN)
}
private fun getItemBitmap(w: Int): Bitmap {
val h = calculateListItemHeight(LIST_ITEM_LINES)
val item = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8)
val canvas = Canvas(item)
canvas.drawColor(Color.argb(255, 0, 0, 0))
val itemPaint = Paint()
itemPaint.isAntiAlias = true
itemPaint.color = Color.argb(0, 0, 0, 0)
itemPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
//Avatar
val rectF = RectF(wSpacing, hSpacing, wSpacing+imageSize, hSpacing+imageSize)
canvas.drawOval(rectF, itemPaint)
val textLeft = rectF.right + hSpacing
val textRight = canvas.width - wSpacing
//Title line
val titleWidth = (textRight - textLeft)*0.5F
rectF.set(textLeft, hSpacing, textLeft+titleWidth, hSpacing+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
//Time stamp
val timeWidth = (textRight - textLeft)*0.2F
rectF.set(textRight-timeWidth, hSpacing, textRight, hSpacing+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
//Text lines
for (i in 0..LIST_ITEM_LINES-1) {
val lineTop = rectF.bottom + hSpacing
rectF.set(textLeft, lineTop, textRight, lineTop+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
}
return item
}
private fun calculateListItemHeight(lines: Int): Int {
return ((lines*lineHeight) + (hSpacing*(lines+1))).toInt()
}
private fun dpToPixels(metrics: DisplayMetrics, dp: Int): Float {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), metrics)
}
private fun spToPixels(metrics: DisplayMetrics, sp: Int): Float {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp.toFloat(), metrics)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
animator?.removeAllUpdateListeners()
animator = null
listItems = null
}
}
Source

Facebook Shimmer Library does support API level 14. I am using it in a project with these settings:
defaultConfig {
....
minSdkVersion 14
targetSdkVersion 23
....
}
With this dependency:
dependencies {
....
compile 'com.facebook.shimmer:shimmer:0.1.0'
}

Related

How to create following types of layout in android

I have tried using ProgressBar or SeekBar but how to add left cut on one side and right side also
I've created a sample custom ConstraintLayout for you to achive what you want.it will behave as a regular constraintLayout but just with a custom background shape.
you have to change numbers and logic according to ui ofcourse, but it will cut your imageView inside it and you can also put progressbar and textViews to create your own ui
package com.customView
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
/**
* Created by Payam Monsef
* At: 2022/Jun/27
*/
class CustomConstraintLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
private var strokeWidth = 4F
private var strokeColor = Color.CYAN
private val cutPosition = CutPosition.TOP_LEFT
//like 0.2 of width will be reduced from cut position
private val reduceWidthMultiplier = 0.2
private var mPath: Path? = null
private var mPathBorder: Path? = null
private var mPaint: Paint? = null
init {
mPath = Path()
mPathBorder = Path()
mPaint = Paint()
mPaint?.style = Paint.Style.STROKE
mPaint?.color = strokeColor
mPaint?.strokeWidth = strokeWidth
setBackgroundColor(Color.TRANSPARENT)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val mW = width.toFloat()
val mH = height.toFloat()
mPathBorder?.apply {
reset()
if (cutPosition == CutPosition.BOTTOM_RIGHT) {
moveTo(0F, 0F)
lineTo(mW, 0F)
lineTo(mW - (reduceWidthMultiplier * mW).toFloat(), mH)
lineTo(0F, mH)
close()
} else {
moveTo((reduceWidthMultiplier * mW).toFloat(), 0F)
lineTo(mW, 0F)
lineTo(mW, mH)
lineTo(0F, mH)
close()
}
}
val offset = strokeWidth / 2
mPath?.apply {
reset()
if (cutPosition == CutPosition.BOTTOM_RIGHT) {
moveTo(offset, offset)
lineTo(mW - offset, offset)
lineTo(mW - (reduceWidthMultiplier * mW).toFloat(), mH - offset)
lineTo(offset, mH - offset)
close()
} else {
moveTo((reduceWidthMultiplier * mW).toFloat() , offset)
lineTo(mW - offset, offset)
lineTo(mW - offset, mH - offset)
lineTo(offset, mH - offset)
close()
}
}
}
override fun onDraw(canvas: Canvas) {
mPathBorder?.let { p ->
mPaint?.let { paint ->
canvas.drawPath(p, paint)
}
}
mPath?.let {
canvas.clipPath(it)
}
}
private enum class CutPosition {
TOP_LEFT, BOTTOM_RIGHT
}
}
Its better than you define above variables as custom attributes.you can read the Google documents to do that

Padding found in the Default time bar and android of exoplayer

Here is the implementation of the Default time bar where I have customized to add segments
XML
<com.sampleapp.androidapp.kotlin.util.exoplayer.SampleAppDefaultTimeBar
android:id="#id/exo_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bar_height="2dp"
app:buffered_color="#color/gb_seek_bar_buffer"
app:layout_constraintStart_toStartOf="parent"
app:played_color="#color/gb_seek_bar_played"
app:scrubber_color="#color/gb_seek_bar_played"
app:unplayed_color="#color/gb_seek_bar_unplayed" />
Code
class SampleAppDefaultTimeBar : DefaultTimeBar {
companion object {
const val DIVIDER_WIDTH = 5F
const val PROGRESS_LEFT_RIGHT_PADDING = 32F
}
// List of points for creating sections
var indicatorPositions: List<Float> = arrayListOf()
var enableSegmentsForPlayer: Boolean = false
//var indicatorPositions: List<Float> = listOf(0.13F, 0.34F, 0.57F, 0.85F, 0.92F)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context,
attrs, defStyle)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private val paintIndicator = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = ContextCompat.getColor(context, R.color.black)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawProgressDivider(canvas)
}
private fun drawProgressDivider(canvas: Canvas) {
if(enableSegmentsForPlayer){
// Only if the segments are enabled - Draw the segments
indicatorPositions.forEach {
val barPositionCenter = it * width()
val barPositionLeft = barPositionCenter - DIVIDER_WIDTH
val barPositionRight = barPositionCenter + DIVIDER_WIDTH
drawOnCanvas(canvas, paintIndicator, barPositionLeft, barPositionRight)
}
}
}
private fun width(): Float {
return measuredWidth.toFloat()
}
private fun drawOnCanvas(canvas: Canvas, paint:Paint, progressLeft: Float, progressRight: Float) {
//val barTop = (measuredHeight - barHeight) / 2
//val barBottom = (measuredHeight + barHeight) / 2
val progressTop = height / 1.8 - minimumHeight / 2
val progressBottom = progressTop / 1.3 + minimumHeight / 2
val barRect = RectF(progressLeft, progressTop.toFloat(),
progressRight, progressBottom.toFloat())
canvas.drawRoundRect(barRect, 50F, 50F, paint)
}
}
Output
As seen in the image above there is padding at the beginning and end of
the bar if the points are found to be at the very begining.
It's the space for the thumb
How to remove that Padding

Pause progress of ObjectAnimator

I'm implementing a custom view which draws some kind ProgressBar view, taking two views as parameters (origin and destination). Like this:
This is the complete class:
class BarView #JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var valueAnimator: ObjectAnimator? = null
private lateinit var path: Path
private val pathMeasure = PathMeasure()
private var pauseProgress: Int = dip(40)
var progress = 0f
set(value) {
field = value.coerceIn(0f, pathMeasure.length)
invalidate()
}
private var originPoint: PointF? = null
private var destinationPoint: PointF? = null
private val cornerEffect = CornerPathEffect(dip(10).toFloat())
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = dip(10f).toFloat()
color = ContextCompat.getColor(context, android.R.color.darker_gray)
pathEffect = cornerEffect
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
if (progress < pathMeasure.length) {
val intervals = floatArrayOf(progress, pathMeasure.length - progress)
val progressEffect = DashPathEffect(intervals, 0f)
linePaint.pathEffect = ComposePathEffect(progressEffect, cornerEffect)
}
canvas.drawPath(path, linePaint)
}
object PROGRESS : Property<BarView, Float>(Float::class.java, "progress") {
override fun set(view: BarView, progress: Float) {
view.progress = progress
}
override fun get(view: BarView) = view.progress
}
private fun startAnimator() {
valueAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 0f, pathMeasure.length).apply {
duration = 500L
interpolator = LinearInterpolator()
}
setPauseListener()
valueAnimator!!.start()
}
fun resume() {
valueAnimator!!.resume()
}
fun reset() {
startAnimator()
}
fun setPoints(originView: View, destinationView: View) {
originPoint = PointF(originView.x + originView.width / 2, 0f)
destinationPoint = PointF(destinationView.x + destinationView.width / 2, 0f)
setPath()
startAnimator()
}
private fun setPath() {
path = Path()
path.moveTo(originPoint!!.x, originPoint!!.y)
path.lineTo(destinationPoint!!.x, destinationPoint!!.y)
pathMeasure.setPath(path, false)
}
private fun setPauseListener() {
valueAnimator!!.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(valueAnimator: ValueAnimator?) {
val progress = valueAnimator!!.getAnimatedValue("progress") as Float
if (progress > pauseProgress) {
valueAnimator.pause()
this#BarView.valueAnimator!!.removeUpdateListener(this)
}
}
})
}
}
What im trying to do is to pause the animation at a specific progress, 40dp in this case:
private fun setPauseListener() {
valueAnimator!!.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(valueAnimator: ValueAnimator?) {
val progress = valueAnimator!!.getAnimatedValue("progress") as Float
if (progress > pauseProgress) {
valueAnimator.pause()
this#BarView.valueAnimator!!.removeUpdateListener(this)
}
}
})
}
But the animations have different speeds since the views have different path lengths, and all of them have to finish in 500ms. They are not pausing at the same distance from the origin:
I tried switching from a LinearInterpolator to a AccelerateInterpolator, to make the start of the animation slower, but i'm still not satisfied with the results.
The next step for me, would be to try to implement a custom TimeInterpolator to make the animation start speed the same no matter how long the path is on each view, but I cannot wrap my head arrow the maths to create the formula needed.
valueAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 0f, pathMeasure.length).apply {
duration = 500L
interpolator = TimeInterpolator { input ->
// formula here
}
}
Any help with that would be well received. Any suggestions about a different approach. What do you think?
1) As I mentioned I would use determinate ProgressBar and regulate bar width by maximum amount of progress, assigned to certain view
2) I would use ValueAnimator.ofFloat with custom interpolator and set progress inside it
3) I would extend my custom interpolator from AccelerateInterpolator to make it look smthng like this:
class CustomInterpolator(val maxPercentage: Float) : AccelerateInterpolator(){
override fun getInterpolation(input: Float): Float {
val calculatedVal = super.getInterpolation(input)
return min(calculatedVal, maxPercentage)
}
}
where maxPercentage is a fraction of view width (from 0 to 1) you bar should occupy
Hope it helps

How to pinch zoom an image picked from gallery

I am start to develop a editing app i can got take images from gallery to app
But i don't know how to pinch zoom it and pan
Please anyone tell me how to do it
It was a small part of my project.
class ZoomView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private val imageBound = RectF()
private val imageMatrixArray = FloatArray(9)
private val scaleDetector: ScaleGestureDetector
private var scale: Float = 1f
private var scalePoint = PointF()
private var translateX: Float = 0f
private var translateY: Float = 0f
private var lastTouchX = 0f
private var lastTouchY = 0f
private var lastDownTouchX = 0f
private var lastDownTouchY = 0f
private var lastGestureX = 0f
private var lastGestureY = 0f
private var isScaling = false
private var activePointerId = -1
private var drawableHeight = -1.0
private var drawableWidth = -1.0
private var minBoxRectSide = 0
init {
scaleDetector = ScaleGestureDetector(context, object : ScaleGestureDetector
.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
isScaling = true
return super.onScaleBegin(detector)
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
scale *= detector.scaleFactor
scale = Math.min(scale, 25f)
scale = Math.max(0.5f, scale)
invalidate()
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
super.onScaleEnd(detector)
isScaling = false
}
})
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
//Timber.d("Image on layout : layoutChanged = $changed")
if (changed) {
drawable?.let {
resetBoxRect(it, left, right, top, bottom)
}
}
}
private fun resetBoxRect(it: Drawable, left: Int, right: Int, top: Int, bottom: Int) {
//Timber.d("image coordinates $left, $top, $right, $bottom")
drawableHeight = it.intrinsicHeight.toDouble()
drawableWidth = it.intrinsicWidth.toDouble()
imageMatrix.getValues(imageMatrixArray)
imageBound.set((left + right) / 2 - imageMatrixArray[Matrix.MSCALE_X] * it.intrinsicWidth / 2,
(bottom - top) / 2 - imageMatrixArray[Matrix.MSCALE_Y] * it.intrinsicHeight / 2,
(left + right) / 2 + imageMatrixArray[Matrix.MSCALE_X] * it.intrinsicWidth / 2,
(bottom - top) / 2 + imageMatrixArray[Matrix.MSCALE_Y] * it.intrinsicHeight / 2)
//Timber.d("Image bound $imageBound, and $boxRect")
minBoxRectSide = (.01f * Math.max(imageBound.bottom - imageBound.top, imageBound.right - imageBound.left)).toInt()
scale = 1f
translateX = 0f
translateY = 0f
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
scalePoint.set(w / 2.toFloat(), h / 2.toFloat())
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
drawable?.let {
if (imageMatrixArray != null) {
resetBoxRect(it, left, right, top, bottom)
}
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
//Timber.d("On touch start")
scaleDetector.onTouchEvent(event)
//Timber.d("On touch gesture sent")
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> onActionDown(event)
MotionEvent.ACTION_MOVE -> onMoveEvent(event)
MotionEvent.ACTION_CANCEL -> activePointerId = -1
MotionEvent.ACTION_UP -> {
activePointerId = -1
}
MotionEvent.ACTION_POINTER_UP -> onActionUp(event)
}
return true
}
private fun onActionDown(event: MotionEvent) {
val actionIndex = event.actionIndex
lastDownTouchX = event.getX(actionIndex)
lastDownTouchY = event.getY(actionIndex)
lastTouchX = event.getX(actionIndex)
lastTouchY = event.getY(actionIndex)
lastGestureX = lastTouchX
lastGestureY = lastTouchY
activePointerId = event.getPointerId(0)
invalidate()
}
private fun onMoveEvent(event: MotionEvent) {
if (!isScaling) {
val index = event.findPointerIndex(activePointerId)
val dx = (event.getX(index) - lastTouchX) / scale
val dy = (event.getY(index) - lastTouchY) / scale
lastTouchX = event.getX(index)
lastTouchY = event.getY(index)
if (Math.abs(translateX + dx) < imageBound.right - imageBound.left)
translateX += dx
if (Math.abs(translateY + dy) < imageBound.bottom - imageBound.top)
translateY += dy
invalidate()
}
}
private fun onActionUp(event: MotionEvent) {
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
if (pointerId == activePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
lastTouchX = event.getX(newPointerIndex)
lastTouchY = event.getY(newPointerIndex)
activePointerId = event.getPointerId(newPointerIndex)
}
}
override fun onDraw(canvas: Canvas) {
canvas.save()
canvas.scale(scale, scale, scalePoint.x, scalePoint.y)
canvas.translate(translateX, translateY)
super.onDraw(canvas)
canvas.restore()
}
}
The view can be used the xml in this way :-
<com.myproject.app.widgets.ZoomView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/abc" />
It will be too broad to write here all the things. Basically there are lot of libraries which provide ImageView with zoom gesture.
You can see their code or use that library as well. Here are two with highest rating on github.
https://github.com/chrisbanes/PhotoView
https://github.com/davemorrissey/subsampling-scale-image-view
You just need to use their ImageView and your ImageView will have the gesture. Like
<com.github.chrisbanes.photoview.PhotoView
android:id="#+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

How to create a battery level indicator in android?

I want to create a battery level indicator as in the image(which i circled). The green part should fill based on the available battery in the device.
Getting the battery percentage from the device like this
registerReceiver(mBatInfoReceiver, new IntentFilter(
Intent.ACTION_BATTERY_CHANGED));
So in the layout i am able to display the battery percentage.
public class BatteryIndicatorActivity extends Activity {
//Create Broadcast Receiver Object along with class definition
private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver() {
#Override
//When Event is published, onReceive method is called
public void onReceive(Context c, Intent i) {
//Get Battery %
int level = i.getIntExtra("level", 0);
TextView tv = (TextView) findViewById(R.id.textfield);
//Set TextView with text
tv.setText("Battery Level: " + Integer.toString(level) + "%");
}
};
But how to create a UI for this type of battery indicator. Is their any api to achieve this?, If not how to create such type of UI.
Your help will be appreciated.
Here is my CustomView for display battery level
class BatteryView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
View(context, attrs, defStyleAttr) {
private var radius: Float = 0f
private var isCharging: Boolean = false
// Top
private var topPaint =
PaintDrawable(Color.WHITE) // I only want to corner top-left and top-right so I use PaintDrawable instead of Paint
private var topRect = Rect()
private var topPaintWidthPercent = 50
private var topPaintHeightPercent = 8
// Border
private var borderPaint = Paint().apply {
color = Color.BLUE
style = Paint.Style.STROKE
}
private var borderRect = RectF()
private var borderStrokeWidthPercent = 8
private var borderStroke: Float = 0f
// Percent
private var percentPaint = Paint()
private var percentRect = RectF()
private var percentRectTopMin = 0f
private var percent: Int = 0
// Charging
private var chargingRect = RectF()
private var chargingBitmap: Bitmap? = null
init {
init(attrs)
chargingBitmap = getBitmap(R.drawable.ic_charging)
}
private fun init(attrs: AttributeSet?) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.BatteryView)
try {
percent = ta.getInt(R.styleable.BatteryView_bv_percent, 0)
isCharging = ta.getBoolean(R.styleable.BatteryView_bv_charging, false)
} finally {
ta.recycle()
}
}
#SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val measureWidth = View.getDefaultSize(suggestedMinimumWidth, widthMeasureSpec)
val measureHeight = (measureWidth * 1.8f).toInt()
setMeasuredDimension(measureWidth, measureHeight)
radius = borderStroke / 2
borderStroke = (borderStrokeWidthPercent * measureWidth).toFloat() / 100
// Top
val topLeft = measureWidth * ((100 - topPaintWidthPercent) / 2) / 100
val topRight = measureWidth - topLeft
val topBottom = topPaintHeightPercent * measureHeight / 100
topRect = Rect(topLeft, 0, topRight, topBottom)
// Border
val borderLeft = borderStroke / 2
val borderTop = topBottom.toFloat() + borderStroke / 2
val borderRight = measureWidth - borderStroke / 2
val borderBottom = measureHeight - borderStroke / 2
borderRect = RectF(borderLeft, borderTop, borderRight, borderBottom)
// Progress
val progressLeft = borderStroke
percentRectTopMin = topBottom + borderStroke
val progressRight = measureWidth - borderStroke
val progressBottom = measureHeight - borderStroke
percentRect = RectF(progressLeft, percentRectTopMin, progressRight, progressBottom)
// Charging Image
val chargingLeft = borderStroke
var chargingTop = topBottom + borderStroke
val chargingRight = measureWidth - borderStroke
var chargingBottom = measureHeight - borderStroke
val diff = ((chargingBottom - chargingTop) - (chargingRight - chargingLeft))
chargingTop += diff / 2
chargingBottom -= diff / 2
chargingRect = RectF(chargingLeft, chargingTop, chargingRight, chargingBottom)
}
override fun onDraw(canvas: Canvas) {
drawTop(canvas)
drawBody(canvas)
if (!isCharging) {
drawProgress(canvas, percent)
} else {
drawCharging(canvas)
}
}
private fun drawTop(canvas: Canvas) {
topPaint.bounds = topRect
topPaint.setCornerRadii(floatArrayOf(radius, radius, radius, radius, 0f, 0f, 0f, 0f))
topPaint.draw(canvas)
}
private fun drawBody(canvas: Canvas) {
borderPaint.strokeWidth = borderStroke
canvas.drawRoundRect(borderRect, radius, radius, borderPaint)
}
private fun drawProgress(canvas: Canvas, percent: Int) {
percentPaint.color = getPercentColor(percent)
percentRect.top = percentRectTopMin + (percentRect.bottom - percentRectTopMin) * (100 - percent) / 100
canvas.drawRect(percentRect, percentPaint)
}
// todo change color
private fun getPercentColor(percent: Int): Int {
if (percent > 50) {
return Color.WHITE
}
if (percent > 30) {
return Color.YELLOW
}
return Color.RED
}
private fun drawCharging(canvas: Canvas) {
chargingBitmap?.let {
canvas.drawBitmap(it, null, chargingRect, null)
}
}
private fun getBitmap(drawableId: Int, desireWidth: Int? = null, desireHeight: Int? = null): Bitmap? {
val drawable = AppCompatResources.getDrawable(context, drawableId) ?: return null
val bitmap = Bitmap.createBitmap(
desireWidth ?: drawable.intrinsicWidth,
desireHeight ?: drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
fun charge() {
isCharging = true
invalidate() // can improve by invalidate(Rect)
}
fun unCharge() {
isCharging = false
invalidate()
}
fun setPercent(percent: Int) {
if (percent > 100 || percent < 0) {
return
}
this.percent = percent
invalidate()
}
fun getPercent(): Int {
return percent
}
}
style.xml
<declare-styleable name="BatteryView">
<attr name="bv_charging" format="boolean" />
<attr name="bv_percent" format="integer" />
</declare-styleable>
drawable/ic_charging.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="368.492"
android:viewportHeight="368.492">
<path
android:fillColor="#FFFFFF"
android:pathData="M297.51,150.349c-1.411,-2.146 -3.987,-3.197 -6.497,-2.633l-73.288,16.498L240.039,7.012c0.39,-2.792 -1.159,-5.498 -3.766,-6.554c-2.611,-1.069 -5.62,-0.216 -7.283,2.054L71.166,217.723c-1.489,2.035 -1.588,4.773 -0.246,6.911c1.339,2.132 3.825,3.237 6.332,2.774l79.594,-14.813l-23.257,148.799c-0.436,2.798 1.096,5.536 3.714,6.629c0.769,0.312 1.562,0.469 2.357,0.469c1.918,0 3.78,-0.901 4.966,-2.517l152.692,-208.621C298.843,155.279 298.916,152.496 297.51,150.349z" />
</vector>
Using
<package.BatteryView
android:id="#+id/battery_view"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:src="#drawable/ic_charging"
app:bv_charging="false"
app:bv_percent="20" />
Github project
Hope it help
There are a lot of ways to do it. Here are few of them:
Use a ProgressBar, make it vertical, make it be drawn in the battery shape
Use a custom View, override onDraw() in it, and draw the battery shape on the canvas
Use a white image with a transparent battery shape. Place it over a view, where you fill a background vertically, or change background view's height.

Categories

Resources