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.
Related
what I want to achieve
what I have
I want to show icons on the xAxis above time but line chart takes icon and places them on the value where temperature is shown. I have searched a lot but could not find the answer. Any help would be appreciated. I have tried a lot of things but all in vein.
private fun setTempChart(hour: ArrayList<Hour>, id: String) {
val entries: MutableList<Entry> = ArrayList()
for (i in hour.indices) {
val code = hour[i].condition.code
val icon =
if (hour[i].is_day == 1) requireActivity().setIconDay(code) else requireActivity().setIconNight(
code
)
entries.add(Entry(i.toFloat(), sharedPreference.temp?.let {
hour[i].temp_c.setCurrentTemperature(
it
).toFloat()
}!!))
}
val dataSet = LineDataSet(entries, "")
dataSet.apply {
lineWidth = 0f
setDrawCircles(false)
setDrawCircleHole(false)
isHighlightEnabled = false
valueTextColor = Color.WHITE
setColors(Color.WHITE)
valueTextSize = 12f
mode = LineDataSet.Mode.CUBIC_BEZIER
setDrawFilled(true)
fillColor = Color.WHITE
valueTypeface = typeface
isDrawIconsEnabled
setDrawIcons(true)
valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return String.format(Locale.getDefault(), "%.0f", value)
}
}
}
val lineData = LineData(dataSet)
chart.apply {
description.isEnabled = false
axisLeft.setDrawLabels(false)
axisRight.setDrawLabels(false)
legend.isEnabled = false
axisLeft.setDrawGridLines(false)
axisRight.setDrawGridLines(false)
axisLeft.setDrawAxisLine(false)
axisRight.setDrawAxisLine(false)
setScaleEnabled(false)
data = lineData
setVisibleXRange(8f, 8f)
animateY(1000)
xAxis.apply {
setDrawAxisLine(false)
textColor = Color.WHITE
setDrawGridLines(false)
setDrawLabels(true)
position = XAxis.XAxisPosition.BOTTOM
textSize = 12f
valueFormatter = MyAxisFormatter(hour, id)
isGranularityEnabled = true
granularity = 1f
labelCount = entries.size
}
}
}
I am using MPAndroidChart library
There isn't a nice built-in way to draw icons like that, but you can do it by making a custom extension of the LineChartRenderer and overriding drawExtras. Then you can get your icons from R.drawable.X and draw them on the canvas wherever you want. There is some work to figure out where to put them to line up with the data points, but you can copy the logic from drawCircles to find that.
Example Custom Renderer
inner class MyRenderer(private val context: Context,
private val iconY: Float,
private val iconSizeDp: Float,
chart: LineDataProvider,
animator: ChartAnimator,
viewPortHandler: ViewPortHandler)
: LineChartRenderer(chart, animator, viewPortHandler) {
private var buffer: FloatArray = listOf(0f,0f).toFloatArray()
override fun drawExtras(c: Canvas) {
super.drawExtras(c)
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
iconSizeDp,
resources.displayMetrics
)
// get the icons you want to draw
val cloudy = ContextCompat.getDrawable(context, R.drawable.cloudy)
val sunny = ContextCompat.getDrawable(context, R.drawable.sunny)
if( cloudy == null || sunny == null ) {
throw RuntimeException("Missing drawables")
}
// Determine icon width in pixels
val w = iconSizePx
val h = iconSizePx
val dataSets = mChart.lineData.dataSets
val phaseY = mAnimator.phaseY
for(dataSet in dataSets) {
mXBounds.set(mChart, dataSet)
val boundsRange = mXBounds.range + mXBounds.min
val transformer = mChart.getTransformer(dataSet.axisDependency)
for(j in mXBounds.min .. boundsRange) {
val e = dataSet.getEntryForIndex(j) ?: break
buffer[0] = e.x
buffer[1] = iconY * phaseY
transformer.pointValuesToPixel(buffer)
if( !mViewPortHandler.isInBoundsRight(buffer[0])) {
break
}
if( !mViewPortHandler.isInBoundsLeft(buffer[0]) ||
!mViewPortHandler.isInBoundsY(buffer[1])) {
continue
}
// Draw the icon centered under the data point, but at a fixed
// vertical position. Here the icon "sits on top" of the
// specified iconY value
val left = (buffer[0]-w/2).roundToInt()
val right = (buffer[0]+w/2).roundToInt()
val top = (buffer[1]-h).roundToInt()
val bottom = (buffer[1]).roundToInt()
// Alternately, use this to center the icon at the
// "iconY" value
//val top = (buffer[1]-h/2).roundToInt()
//val bottom = (buffer[1]+h/2).roundToInt()
// Use whatever logic you want to select which icon
// to use at each position
val icon = if( e.y > 68f ) {
sunny
}
else {
cloudy
}
icon.setBounds(left, top, right, bottom)
icon.draw(c)
}
}
}
}
Using the Custom Renderer
val iconY = 41f
val iconSizeDp = 40f
chart.renderer = MyRenderer(this, iconY, iconSizeDp,
chart, chart.animator, chart.viewPortHandler)
(other formatting)
val chart = findViewById<LineChart>(R.id.chart)
chart.axisRight.isEnabled = false
val yAx = chart.axisLeft
yAx.setDrawLabels(false)
yAx.setDrawGridLines(false)
yAx.setDrawAxisLine(false)
yAx.axisMinimum = 40f
yAx.axisMaximum = 80f
val xAx = chart.xAxis
xAx.setDrawLabels(false)
xAx.position = XAxis.XAxisPosition.BOTTOM
xAx.setDrawGridLines(false)
xAx.setDrawAxisLine(false)
xAx.axisMinimum = 0.5f
xAx.axisMaximum = 5.5f
xAx.granularity = 0.5f
val x = listOf(0,1,2,3,4,5,6)
val y = listOf(60,65,66,70,65,50,55)
val e = x.zip(y).map { Entry(it.first.toFloat(), it.second.toFloat())}
val line = LineDataSet(e, "temp")
line.setDrawValues(true)
line.setDrawCircles(false)
line.circleRadius = 20f // makes the text offset up
line.valueTextSize = 20f
line.color = Color.BLACK
line.lineWidth = 2f
line.setDrawFilled(true)
line.fillColor = Color.BLACK
line.fillAlpha = 50
line.mode = LineDataSet.Mode.CUBIC_BEZIER
line.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return "%.0f F".format(value)
}
}
chart.data = LineData(line)
chart.description.isEnabled = false
chart.legend.isEnabled = false
Gives the desired effect
I am a beginner. I have started learning custom views these days, and there are almost no problems in the process.
When I went to Google to solve this problem, some people proposed solutions, but none of them was successful. I use a custom view written by Kotlin.
This is my custom view class,and the name is MyView.kt
package com.example.demos
import android.R
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import kotlin.math.min
class MyView : View {
// init
private lateinit var arcPaint: Paint
private lateinit var progressTextPaint: Paint
// private lateinit var arcPaintColor: Color
private var arcPaintColor = Color.BLACK
// private lateinit var progressTextPaintColor: Color
private var progressTextPaintColor = Color.BLACK
private var angle = 0f
private var progress: Float = angle / 3.6f
// get/set
fun setArcPaintColor(color: Int) {
arcPaintColor = color
}
fun getArcPaintColor(): Int {
return arcPaintColor
}
fun setProgressTextPaintColor(color: Int) {
progressTextPaintColor = color
}
fun getProgressTextPaintColor(): Int {
return progressTextPaintColor
}
fun setAngle(float: Float) {
angle = float
progress = angle / 3.6f
invalidate()
}
fun getAngle(): Float {
return angle
}
fun setProgress(float: Float) {
progress = float
angle = progress * 3.6f
invalidate()
}
fun getProgress(): Float {
return progress
}
/*call method initPaint()*/
constructor(context: Context) : super(context) {
initPaint()
}
constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet) {
initPaint()
}
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : super(
context,
attributeSet,
defStyleAttr
) {
arcPaintColor = typedArray.getColor(R.styleable.arcPaintColor,)
initPaint()
}
/*override onDraw(),draw view*/
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
drawView(canvas)
}
//init paints
private fun initPaint() {
arcPaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
it.color = arcPaintColor
it.strokeWidth = 5f
it.strokeWidth = 40f
it.style = Paint.Style.STROKE
it.strokeCap = Paint.Cap.ROUND
}
progressTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
it.color = progressTextPaintColor
// it.color = Color.GREEN
// it.setStrokeWidth(5f)
it.style = Paint.Style.FILL
it.textSize = 50f
}
}
/*draw view*/
private fun drawView(canvas: Canvas?) {
val displayWidth = width
val displayHeight = height
/*get center of circle*/
val centerX = (displayWidth / 2).toFloat()
val centerY = (displayHeight / 2).toFloat()
/*get radius*/
val radius = min(displayWidth, displayHeight) / 4
val rectF = RectF(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius
)
canvas?.drawArc(
rectF,
0f,
angle,
false,
arcPaint
)
canvas?.drawText(
"${String.format("%.1f", progress)}%",
centerX - progressTextPaint.measureText("${String.format("%.1f", progress)}%") / 2,
centerY,
progressTextPaint
)
}
}
This is my xml file of custom attributes
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="arcPaintColor" format="color"/>
<attr name="progressTextPaintColor" format="color"/>
</declare-styleable>
</resources>
the xml file of my custom attribute
The issue is that you are importing android.R
You need to import the [you package name].R version instead.
You are not picking up the custom attributes correctly. This is how it should be done:
val typedArray =
context.theme.obtainStyledAttributes(
attributeSet, R.styleable.MyView, 0, 0
)
arcPaintColor = typedArray.getColor(R.styleable.MyView_arcPaintColor, 0)
typedArray.recycle() // Important!
initPaint()
You will have to make sure that this code executes in each of your constructors. The first zero will be replace by defStyleAttr when that is available. I suggest that you integrate the above code into initPaint().
See the documentation for obtainStyledAttributes().
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
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'
}
What is the best way of generating (in code) a letter avatar like in Gmail?
Here you have an example
https://drive.google.com/folderview?id=0B0Fhz5fDg1njSmpUakhhZllEWHM&usp=sharing
It should look like that:
This is what I've used once.. please try and modify according to your requirements.
public class LetterAvatar extends ColorDrawable {
Paint paint = new Paint();
Rect bounds = new Rect();
String pLetters;
private float ONE_DP = 0.0f;
private Resources pResources;
private int pPadding;
int pSize = 0;
float pMesuredTextWidth;
int pBoundsTextwidth;
int pBoundsTextHeight;
public LetterAvatar (Context context, int color, String letter, int paddingInDp) {
super(color);
this.pLetters = letter;
this.pResources = context.getResources();
ONE_DP = 1 * pResources.getDisplayMetrics().density;
this.pPadding = Math.round(paddingInDp * ONE_DP);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
paint.setAntiAlias(true);
do {
paint.setTextSize(++pSize);
paint.getTextBounds(pLetters, 0, pLetters.length(), bounds);
} while ((bounds.height() < (canvas.getHeight() - pPadding)) && (paint.measureText(pLetters) < (canvas.getWidth() - pPadding)));
paint.setTextSize(pSize);
pMesuredTextWidth = paint.measureText(pLetters);
pBoundsTextHeight = bounds.height();
float xOffset = ((canvas.getWidth() - pMesuredTextWidth) / 2);
float yOffset = (int) (pBoundsTextHeight + (canvas.getHeight() - pBoundsTextHeight) / 2);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setColor(0xffffffff);
canvas.drawText(pLetters, xOffset, yOffset, paint);
}
}
then set new LetterAvatar(context, colorCode, letters, padding) in your imageview.setdrawable
If you are asking only about avatar on the left in that ListView.
Use ImageView, and if you have user avatar - put it there, if you don't have avatar - use .drawText("R") function to draw canvas and put it in ImageView using setImageDrawable.
Judging from the image you provided above, this can be done using a custom listview. The avatar you are looking for should be an imageview in your custom layout and inflated into the listview. I suggest you start here. The gravar can be an image drawable in your resource folder,
http://www.ezzylearning.com/tutorial.aspx?tid=1763429
Below is a class that generates an Image avatar both a circle and Square. Source is here
class AvatarGenerator {
companion object {
lateinit var uiContext: Context
var texSize = 0F
fun avatarImage(context: Context, size: Int, shape: Int, name: String): BitmapDrawable {
uiContext = context
val width = size
val hieght = size
texSize = calTextSize(size)
val label = firstCharacter(name)
val textPaint = textPainter()
val painter = painter()
val areaRect = Rect(0, 0, width, width)
if (shape == 0) {
painter.color = RandomColors().getColor()
} else {
painter.color = Color.TRANSPARENT
}
val bitmap = Bitmap.createBitmap(width, width, ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawRect(areaRect, painter)
//reset painter
if (shape == 0) {
painter.color = Color.TRANSPARENT
} else {
painter.color = RandomColors().getColor()
}
val bounds = RectF(areaRect)
bounds.right = textPaint.measureText(label, 0, 1)
bounds.bottom = textPaint.descent() - textPaint.ascent()
bounds.left += (areaRect.width() - bounds.right) / 2.0f
bounds.top += (areaRect.height() - bounds.bottom) / 2.0f
canvas.drawCircle(width.toFloat() / 2, hieght.toFloat() / 2, width.toFloat() / 2, painter)
canvas.drawText(label, bounds.left, bounds.top - textPaint.ascent(), textPaint)
return BitmapDrawable(uiContext.resources, bitmap)
}
private fun firstCharacter(name: String): String {
return name.first().toString().toUpperCase()
}
private fun textPainter(): TextPaint {
val textPaint = TextPaint()
textPaint.textSize = texSize * uiContext.resources.displayMetrics.scaledDensity
textPaint.color = Color.WHITE
return textPaint
}
private fun painter(): Paint {
return Paint()
}
private fun calTextSize(size: Int): Float {
return (size / 3.125).toFloat()
}
}
}
You can then pass context, a string, size, and the shape to generate 1/0
imageView.setImageDrawable(
AvatarGenerator.avatarImage(
this,
200,
1,
"Skyways"
)