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"
)
Related
I have LinearLayout, Can I give the alpha attribute to LinearLayout but in some positions, Not to the whole of view?
For example give android:alpha="0.5" to LinearLayout, Starting 150px to 300px ( The Width ) and 500px to 650px ( The Height )
There are likely a number of ways to approach this. What occurs to me is to write a custom drawable that will set the background the way you want.
MyBackgrondDrawable.kt (Kotlin version)
class MyBackgroundDrawable : Drawable() {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = 0x0000ff
}
private var centerRect = Rect()
override fun draw(canvas: Canvas) {
canvas.withSave {
// Find the center of the canvas.
val centerWidth = (bounds.width() / 2)
val centerHeight = (bounds.height() / 2)
// Define our box around the center.
centerRect.left = centerWidth - HALF_BOX_SIZE
centerRect.top = centerHeight - HALF_BOX_SIZE
centerRect.right = centerWidth + HALF_BOX_SIZE
centerRect.bottom = centerHeight + HALF_BOX_SIZE
// Draw the paint with this alpha inside of the center box.
paint.alpha = 0x55
withClip(centerRect) {
drawPaint(paint)
}
// Invert the clipped region.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
clipOutRect(centerRect)
} else {
clipRect(centerRect, Region.Op.DIFFERENCE)
}
// Draw the paint with this alpha outside of the center box.
paint.alpha = 0xff
drawPaint(paint)
}
}
override fun setAlpha(alpha: Int) {
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
private companion object {
const val BOX_SIZE = 150 // width & height in pixels
const val HALF_BOX_SIZE = BOX_SIZE / 2
}
}
MyBackgrondDrawable.java (Java version)
public class MyBackgroundDrawable extends Drawable {
private final Paint paint = new Paint();
private final RectF centerRect = new RectF();
MyBackgroundDrawableJava() {
paint.setStyle(Paint.Style.FILL);
paint.setColor(0x0000ff);
}
#Override
public void draw(Canvas canvas) {
canvas.save();
// Find the center of the canvas.
Rect bounds = getBounds();
float centerWidth = (bounds.width() / 2f);
float centerHeight = (bounds.height() / 2f);
// Define our box around the center.
centerRect.left = centerWidth - HALF_BOX_SIZE;
centerRect.top = centerHeight - HALF_BOX_SIZE;
centerRect.right = centerWidth + HALF_BOX_SIZE;
centerRect.bottom = centerHeight + HALF_BOX_SIZE;
// Draw the paint with this alpha inside of the center box.
paint.setAlpha(0x55);
canvas.clipRect(centerRect);
canvas.drawPaint(paint);
canvas.restore();
canvas.save();
// Invert the clipped region.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas.clipOutRect(centerRect);
} else {
canvas.clipRect(centerRect, Region.Op.DIFFERENCE);
}
// Draw the paint with this alpha outside of the center box.
paint.setAlpha(0xff);
canvas.drawPaint(paint);
canvas.restore();
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
private static final int BOX_SIZE = 150; // width & height in pixels
private static final int HALF_BOX_SIZE = BOX_SIZE / 2;
}
With this drawable, you can set it to the background of the layout like this:
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.background = MyBackgroundDrawable()
If you have a minSdk of API 24 or higher, you can define this drawable in a drawable file as follows:
cutout_with_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<drawable xmlns:tools="http://schemas.android.com/tools"
class="com.example.backgroundalpha.MyBackgroundDrawable"
tools:targetApi="n" />
You can then set the background in XML like so:
<androidx.constraintlayout.widget.ConstraintLayout
...
android:background="#drawable/cutout_with_alpha">
I have a custom squircle android view. I am drawing a Path but and I cannot fill the path with color.
StackOverflow is asking me to provide more detail, but I don't think I can explain this better. All I need is to fill the path with code. If you need to see the whole class let me know.
init {
val typedValue = TypedValue()
val theme = context!!.theme
theme.resolveAttribute(R.attr.colorAccentTheme, typedValue, true)
paint.color = typedValue.data
paint.strokeWidth = 6f
paint.style = Paint.Style.FILL_AND_STROKE
paint.isAntiAlias = true
shapePadding = 10f
}
open fun onLayoutInit() {
val hW = (this.measuredW / 2) - shapePadding
val hH = (this.measuredH / 2) - shapePadding
/*
Returns a series of Vectors along the path
of the squircle
*/
points = Array(360) { i ->
val angle = toRadians(i.toDouble())
val x = pow(abs(cos(angle)), corners) * hW * sgn(cos(angle))
val y = pow(abs(sin(angle)), corners) * hH * sgn(sin(angle))
Pair(x.toFloat(), y.toFloat())
}
/*
Match the path to the points
*/
for (i in 0..points.size - 2) {
val p1 = points[i]
val p2 = points[i + 1]
path.moveTo(p1.first, p1.second)
path.lineTo(p2.first, p2.second)
}
/*
Finish closing the path's points
*/
val fst = points[0]
val lst = points[points.size - 1]
path.moveTo(lst.first, lst.second)
path.lineTo(fst.first, fst.second)
path.fillType = Path.FillType.EVEN_ODD
path.close()
postInvalidate()
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
canvas?.translate(measuredW / 2, measuredH / 2)
canvas?.drawPath(path, paint)
canvas?.restore()
super.onDraw(canvas)
}
}
The path stroke works fine, but I can't fill it with color.
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.
I have a SurfaceView for barcode scan (by this tutorial).
<SurfaceView
android:id="#+id/camera_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
Need to set there cropped camera preview with 1/4 height of the screen.
I'm set a view size based on screen size:
cameraView = (SurfaceView) findViewById(R.id.camera_view);
final DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels / 4);
cameraView.setLayoutParams(layoutParams);
But camera preview just shrinks inside!
Here is how i set it
final BarcodeDetector barcodeDetector =
new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.ALL_FORMATS)
.build();
final CameraSource cameraSource = new CameraSource
.Builder(this, barcodeDetector)
.setRequestedPreviewSize(metrics.widthPixels, metrics.heightPixels / 4)
.setAutoFocusEnabled(true)
.build();
cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
cameraSource.start(cameraView.getHolder());
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
cameraSource.stop();
}
});
How can i crop camera preview? Need just a bottom 1/4 part.
Also need to add a transparent layer with rectangle viewfinder over it.
What you are probably looking to use is a TextureView. SurfaceViews are not designed to be cropped - the video display adjusting to the size of the SurfaceView is the expected behaviour.
On the other hand, TextureViews, which are available from API level 14+, allow you to set a .setTransform(Matrix matrix), where the matrix contains data concerning how to scale the video, and .setTranslationX(float x) and .setTranslationY(float y) to crop the video. A good example is available here.
If you absolutely must use SurfaceView, I would recommend looking into using a GLSurfaceView with a custom renderer that scales the video. A good starting point would be here.
I solved it with a ViewGroup.
In layout:
<com.superup.smartshelf.barcode.BarcodeCameraPreview
android:id="#+id/preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.25"/>
In class:
public class BarcodeCameraPreview extends ViewGroup {
private SurfaceView mSurfaceView;
public BarcodeCameraPreview(final Context context, AttributeSet attrs) {
super(context, attrs);
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// crop view
int actualPreviewWidth = getResources().getDisplayMetrics().widthPixels;
int actualPreviewHeight = getResources().getDisplayMetrics().heightPixels;
if (mSurfaceView != null) {
mSurfaceView.layout(0, 0, actualPreviewWidth, actualPreviewHeight);
}
}
// camera methods
}
In activity:
BarcodeCameraPreview camera = (BarcodeCameraPreview) findViewById(R.id.preview);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels / 4);
camera.setLayoutParams(layoutParams);
You cannot achieve crop by shrinking the view. You can overlay the camera preview with another layout and effectively hide the upper 3/4 of the picture.
Hi I have found the ultimate solution from one of the google Codelabs projects
https://codelabs.developers.google.com/codelabs/mlkit-android-translate?hl=en#0
// overlay is your surface view's id
overlay.apply {
setZOrderOnTop(true)
holder.setFormat(PixelFormat.TRANSPARENT)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
p0: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
}
override fun surfaceCreated(p0: SurfaceHolder) {
holder?.let { drawOverlay(it, 65, 15) }
}
})
}
//--------------Below function is to draw the cropped overlay--------------
private fun drawOverlay(
holder: SurfaceHolder,
heightCropPercent: Int,
widthCropPercent: Int
) {
val canvas = holder.lockCanvas()
val bgPaint = Paint().apply {
alpha = 160
}
canvas.drawPaint(bgPaint)
val rectPaint = Paint()
rectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
rectPaint.style = Paint.Style.FILL
rectPaint.color = Color.WHITE
val outlinePaint = Paint()
outlinePaint.style = Paint.Style.STROKE
outlinePaint.color = Color.WHITE
outlinePaint.strokeWidth = 4f
val surfaceWidth = holder.surfaceFrame.width()
val surfaceHeight = holder.surfaceFrame.height()
val cornerRadius = 25f
// Set rect centered in frame
val rectTop = surfaceHeight * heightCropPercent / 2 / 100f
val rectLeft = surfaceWidth * widthCropPercent / 2 / 100f
val rectRight = surfaceWidth * (1 - widthCropPercent / 2 / 100f)
val rectBottom = surfaceHeight * (1 - heightCropPercent / 2 / 100f)
val rect = RectF(rectLeft, rectTop, rectRight, rectBottom)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, rectPaint
)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, outlinePaint
)
val textPaint = Paint()
textPaint.color = Color.WHITE
textPaint.textSize = 50F
val overlayText = getString(R.string.overlay_help)
val textBounds = Rect()
textPaint.getTextBounds(overlayText, 0, overlayText.length, textBounds)
val textX = (surfaceWidth - textBounds.width()) / 2f
val textY = rectBottom + textBounds.height() + 15f // put text below rect and 15f padding
canvas.drawText(getString(R.string.overlay_help), textX, textY, textPaint)
holder.unlockCanvasAndPost(canvas)
}
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'
}