I am using kotlin coroutinescope for loading thumnails from video uri and add imageview with that bitmaps in linear layout.
Currently after all thumnail loaded, I am adding into linearlayout. Can anyone suggest me for getting one by one bitmao and adding into linearlayout?
private fun loadThumbnails(uri: Uri) {
val metaDataSource = MediaMetadataRetriever()
metaDataSource.setDataSource(context, uri)
val videoLength = (metaDataSource.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION).toInt() * 1000).toLong()
val thumbnailCount = 8
val interval = videoLength / thumbnailCount
var listOfImage: ArrayList<Bitmap?> = ArrayList()
for (i in 0 until thumbnailCount - 1) {
try {
var bitmap: Bitmap? = null
val job = CoroutineScope(Dispatchers.IO).launch {
val frameTime = i * interval
bitmap = metaDataSource.getFrameAtTime(frameTime, MediaMetadataRetriever.OPTION_CLOSEST)
bitmap?.let {
val targetWidth: Int
val targetHeight: Int
if (it.height > it.width) {
targetHeight = frameDimension
val percentage = frameDimension.toFloat() / it.height
targetWidth = (it.width * percentage).toInt()
} else {
targetWidth = frameDimension
val percentage = frameDimension.toFloat() / it.width
targetHeight = (it.height * percentage).toInt()
}
bitmap = Bitmap.createScaledBitmap(it, targetWidth, targetHeight, false)
}
listOfImage.add(bitmap)
metaDataSource.release()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
listOfImage.forEach {
container_thumbnails.addView(ThumbnailView(context).apply { setImageBitmap(it) })
}
}
Please try next approach:
val job = CoroutineScope(Dispatchers.Main).launch {
val frameTime = i * interval
val bitmap = loadBitmap(frameTime) // loads bitmap asynchronously using withContext(Dispatchers.IO)
// ... use bitmap to set into a view
}
suspend fun loadBitmap(frameTime: Int): Bitmap? = withContext(Dispatchers.IO) {
bitmap = metaDataSource.getFrameAtTime(frameTime, MediaMetadataRetriever.OPTION_CLOSEST)
bitmap?.let {
val targetWidth: Int
val targetHeight: Int
if (it.height > it.width) {
targetHeight = frameDimension
val percentage = frameDimension.toFloat() / it.height
targetWidth = (it.width * percentage).toInt()
} else {
targetWidth = frameDimension
val percentage = frameDimension.toFloat() / it.width
targetHeight = (it.height * percentage).toInt()
}
Bitmap.createScaledBitmap(it, targetWidth, targetHeight, false)
}
}
Related
I'm trying to get two images from an url and then I have a Composable that needs two bitmaps to draw them in a Canvas, I've tried it but the canvas don't get painted am I missing something?
val overlayImage =
"https://st2.depositphotos.com/1400069/5999/i/600/depositphotos_59995765-stock-photo-abstract-galaxy-background.jpg"
val baseImage =
"https://www.vitrinesdocomercio.com/uploads/1/3/9/4/13943900/1278180_orig.jpg"
val overlayImageLoaded = rememberAsyncImagePainter(
model = overlayImage,
)
val baseImageLoaded = rememberAsyncImagePainter(
model = baseImage
)
var overlayBitmap = remember<Bitmap?> {
null
}
var baseBitmap = remember<Bitmap?> {
null
}
val overlayImageLoadedState = overlayImageLoaded.state
if (overlayImageLoadedState is AsyncImagePainter.State.Success) {
overlayBitmap = overlayImageLoadedState.result.drawable.toBitmap()
}
val baseImageLoadedState = baseImageLoaded.state
if (baseImageLoadedState is AsyncImagePainter.State.Success) {
baseBitmap = baseImageLoadedState.result.drawable.toBitmap()
}
MyCanvasComposable(baseBitmap, overlayBitmap)
You should assign size for painter to be created, otherwise it returns error
val overlayPainter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(overlayImage)
.size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
.build()
)
val basePainter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(baseImage)
.size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
.build()
)
Result
You can send both of the ImageBitmaps when both states are success with
#Composable
private fun MyComposable() {
val overlayImage =
"https://st2.depositphotos.com/1400069/5999/i/600/depositphotos_59995765-stock-photo-abstract-galaxy-background.jpg"
val baseImage =
"https://www.vitrinesdocomercio.com/uploads/1/3/9/4/13943900/1278180_orig.jpg"
val overlayPainter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(overlayImage)
.size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
.build()
)
val basePainter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(baseImage)
.size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
.build()
)
val overlayImageLoadedState = overlayPainter.state
val baseImageLoadedState = basePainter.state
if (
baseImageLoadedState is AsyncImagePainter.State.Success &&
overlayImageLoadedState is AsyncImagePainter.State.Success
) {
SideEffect {
println("🔥 COMPOSING...")
}
val baseImageBitmap =
baseImageLoadedState.result.drawable.toBitmap()
.asImageBitmap()
val overlayImageBitmap =
overlayImageLoadedState.result.drawable
.toBitmap()
.asImageBitmap()
EraseBitmapSample(
baseImageBitmap = baseImageBitmap,
overlayImageBitmap = overlayImageBitmap,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(4 / 3f)
)
}
}
And what you wish to achieve
#Composable
fun EraseBitmapSample(
overlayImageBitmap: ImageBitmap,
baseImageBitmap: ImageBitmap,
modifier: Modifier
) {
var matchPercent by remember {
mutableStateOf(100f)
}
BoxWithConstraints(modifier) {
// Path used for erasing. In this example erasing is faked by drawing with canvas color
// above draw path.
val erasePath = remember { Path() }
var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
// This is our motion event we get from touch motion
var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
// This is previous motion event before next touch is saved into this current position
var previousPosition by remember { mutableStateOf(Offset.Unspecified) }
val imageWidth = constraints.maxWidth
val imageHeight = constraints.maxHeight
val drawImageBitmap = remember {
Bitmap.createScaledBitmap(
overlayImageBitmap.asAndroidBitmap(),
imageWidth,
imageHeight,
false
)
.asImageBitmap()
}
// Pixels of scaled bitmap, we scale it to composable size because we will erase
// from Composable on screen
val originalPixels: IntArray = remember {
val buffer = IntArray(imageWidth * imageHeight)
drawImageBitmap
.readPixels(
buffer = buffer,
startX = 0,
startY = 0,
width = imageWidth,
height = imageHeight
)
buffer
}
val erasedBitmap: ImageBitmap = remember {
Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).asImageBitmap()
}
val canvas: Canvas = remember {
Canvas(erasedBitmap)
}
val paint = remember {
Paint()
}
val erasePaint = remember {
Paint().apply {
blendMode = BlendMode.Clear
this.style = PaintingStyle.Stroke
strokeWidth = 30f
}
}
canvas.apply {
val nativeCanvas = this.nativeCanvas
val canvasWidth = nativeCanvas.width.toFloat()
val canvasHeight = nativeCanvas.height.toFloat()
when (motionEvent) {
MotionEvent.Down -> {
erasePath.moveTo(currentPosition.x, currentPosition.y)
previousPosition = currentPosition
}
MotionEvent.Move -> {
erasePath.quadraticBezierTo(
previousPosition.x,
previousPosition.y,
(previousPosition.x + currentPosition.x) / 2,
(previousPosition.y + currentPosition.y) / 2
)
previousPosition = currentPosition
}
MotionEvent.Up -> {
erasePath.lineTo(currentPosition.x, currentPosition.y)
currentPosition = Offset.Unspecified
previousPosition = currentPosition
motionEvent = MotionEvent.Idle
matchPercent = compareBitmaps(
originalPixels,
erasedBitmap,
imageWidth,
imageHeight
)
}
else -> Unit
}
with(canvas.nativeCanvas) {
drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
drawImageRect(
image = drawImageBitmap,
dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
paint = paint
)
drawPath(
path = erasePath,
paint = erasePaint
)
}
}
val canvasModifier = Modifier.pointerMotionEvents(
Unit,
onDown = { pointerInputChange ->
motionEvent = MotionEvent.Down
currentPosition = pointerInputChange.position
pointerInputChange.consume()
},
onMove = { pointerInputChange ->
motionEvent = MotionEvent.Move
currentPosition = pointerInputChange.position
pointerInputChange.consume()
},
onUp = { pointerInputChange ->
motionEvent = MotionEvent.Up
pointerInputChange.consume()
},
delayAfterDownInMillis = 20
)
Image(
bitmap = baseImageBitmap,
contentDescription = null
)
Image(
modifier = canvasModifier
.clipToBounds()
.matchParentSize()
.border(2.dp, Color.Green),
bitmap = erasedBitmap,
contentDescription = null,
contentScale = ContentScale.FillBounds
)
}
Text(
text = "Bitmap match ${matchPercent}%",
color = Color.Red,
fontSize = 22.sp,
)
}
I'm using the CameraX Image Analyzer to scan the first image below. The camera manages to analyse it and I'm able to convert it into a bitmap, but when it converts, it appears dark (the second image). How do I get the bitmap to not appear dark?
The code below handles the image analysis and the conversion to a bitmap.
private class ImageAnalyzer(mContext: Context, mOnImageRequest: (File) -> Unit) : ImageAnalysis.Analyzer {
val context = mContext
val onImageRequest = mOnImageRequest
#SuppressLint("UnsafeExperimentalUsageError", "UnsafeOptInUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
var bitmap = Bitmap.createBitmap(mediaImage.width, mediaImage.height, Bitmap.Config.ARGB_8888)
bitmap.setHasAlpha(true)
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 80, baos)
val converter = YuvToRgbConverter(context)
converter.yuvToRgb(mediaImage, bitmap)
}
}}
class YuvToRgbConverter(context: Context) {
private val rs = RenderScript.create(context)
private val scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
private var pixelCount: Int = -1
private lateinit var yuvBuffer: ByteArray
private lateinit var inputAllocation: Allocation
private lateinit var outputAllocation: Allocation
#Synchronized
fun yuvToRgb(image: Image, output: Bitmap) {
if (!::yuvBuffer.isInitialized) {
pixelCount = image.cropRect.width() * image.cropRect.height()
val pixelSizeBits = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)
yuvBuffer = ByteArray(pixelCount * pixelSizeBits / 8)
}
imageToByteArray(image, yuvBuffer)
if (!::inputAllocation.isInitialized) {
val elemType = Type.Builder(rs, Element.YUV(rs)).setYuvFormat(ImageFormat.NV21).create()
inputAllocation = Allocation.createSized(rs, elemType.element, yuvBuffer.size)
}
if (!::outputAllocation.isInitialized) {
outputAllocation = Allocation.createFromBitmap(rs, output)
}
inputAllocation.copyFrom(yuvBuffer)
scriptYuvToRgb.setInput(inputAllocation)
scriptYuvToRgb.forEach(outputAllocation)
outputAllocation.copyTo(output)
}
private fun imageToByteArray(image: Image, outputBuffer: ByteArray) {
assert(image.format == ImageFormat.YUV_420_888)
val imageCrop = image.cropRect
val imagePlanes = image.planes
imagePlanes.forEachIndexed { planeIndex, plane ->
val outputStride: Int
var outputOffset: Int
when (planeIndex) {
0 -> {
outputStride = 1
outputOffset = 0
}
1 -> {
outputStride = 2
outputOffset = pixelCount + 1
}
2 -> {
outputStride = 2
outputOffset = pixelCount
}
else -> {
return#forEachIndexed
}
}
val planeBuffer = plane.buffer
val rowStride = plane.rowStride
val pixelStride = plane.pixelStride
val planeCrop = if (planeIndex == 0) {
imageCrop
} else {
Rect(
imageCrop.left / 2,
imageCrop.top / 2,
imageCrop.right / 2,
imageCrop.bottom / 2
)
}
val planeWidth = planeCrop.width()
val planeHeight = planeCrop.height()
val rowBuffer = ByteArray(plane.rowStride)
val rowLength = if (pixelStride == 1 && outputStride == 1) {
planeWidth
} else {
(planeWidth - 1) * pixelStride + 1
}
for (row in 0 until planeHeight) {
planeBuffer.position(
(row + planeCrop.top) * rowStride + planeCrop.left * pixelStride)
if (pixelStride == 1 && outputStride == 1) {
planeBuffer.get(outputBuffer, outputOffset, rowLength)
outputOffset += rowLength
} else {
planeBuffer.get(rowBuffer, 0, rowLength)
for (col in 0 until planeWidth) {
outputBuffer[outputOffset] = rowBuffer[col * pixelStride]
outputOffset += outputStride
}
}
}
}
}}
was wondering if anyone knows how to produce elliptical/arched list in Compose?
Something along these lines:
Not sure If I'm overlooking an 'easy' way of doing it in Compose. Cheers!
I have an article here that shows how to do this. It is not a LazyList in that it computes all the items (but only renders the visible ones); you can use this as a starting point to build upon.
The full code is below as well:
data class CircularListConfig(
val contentHeight: Float = 0f,
val numItems: Int = 0,
val visibleItems: Int = 0,
val circularFraction: Float = 1f,
val overshootItems: Int = 0,
)
#Stable
interface CircularListState {
val verticalOffset: Float
val firstVisibleItem: Int
val lastVisibleItem: Int
suspend fun snapTo(value: Float)
suspend fun decayTo(velocity: Float, value: Float)
suspend fun stop()
fun offsetFor(index: Int): IntOffset
fun setup(config: CircularListConfig)
}
class CircularListStateImpl(
currentOffset: Float = 0f,
) : CircularListState {
private val animatable = Animatable(currentOffset)
private var itemHeight = 0f
private var config = CircularListConfig()
private var initialOffset = 0f
private val decayAnimationSpec = FloatSpringSpec(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow,
)
private val minOffset: Float
get() = -(config.numItems - 1) * itemHeight
override val verticalOffset: Float
get() = animatable.value
override val firstVisibleItem: Int
get() = ((-verticalOffset - initialOffset) / itemHeight).toInt().coerceAtLeast(0)
override val lastVisibleItem: Int
get() = (((-verticalOffset - initialOffset) / itemHeight).toInt() + config.visibleItems)
.coerceAtMost(config.numItems - 1)
override suspend fun snapTo(value: Float) {
val minOvershoot = -(config.numItems - 1 + config.overshootItems) * itemHeight
val maxOvershoot = config.overshootItems * itemHeight
animatable.snapTo(value.coerceIn(minOvershoot, maxOvershoot))
}
override suspend fun decayTo(velocity: Float, value: Float) {
val constrainedValue = value.coerceIn(minOffset, 0f).absoluteValue
val remainder = (constrainedValue / itemHeight) - (constrainedValue / itemHeight).toInt()
val extra = if (remainder <= 0.5f) 0 else 1
val target =((constrainedValue / itemHeight).toInt() + extra) * itemHeight
animatable.animateTo(
targetValue = -target,
initialVelocity = velocity,
animationSpec = decayAnimationSpec,
)
}
override suspend fun stop() {
animatable.stop()
}
override fun setup(config: CircularListConfig) {
this.config = config
itemHeight = config.contentHeight / config.visibleItems
initialOffset = (config.contentHeight - itemHeight) / 2f
}
override fun offsetFor(index: Int): IntOffset {
val maxOffset = config.contentHeight / 2f + itemHeight / 2f
val y = (verticalOffset + initialOffset + index * itemHeight)
val deltaFromCenter = (y - initialOffset)
val radius = config.contentHeight / 2f
val scaledY = deltaFromCenter.absoluteValue * (config.contentHeight / 2f / maxOffset)
val x = if (scaledY < radius) {
sqrt((radius * radius - scaledY * scaledY))
} else {
0f
}
return IntOffset(
x = (x * config.circularFraction).roundToInt(),
y = y.roundToInt()
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CircularListStateImpl
if (animatable.value != other.animatable.value) return false
if (itemHeight != other.itemHeight) return false
if (config != other.config) return false
if (initialOffset != other.initialOffset) return false
if (decayAnimationSpec != other.decayAnimationSpec) return false
return true
}
override fun hashCode(): Int {
var result = animatable.value.hashCode()
result = 31 * result + itemHeight.hashCode()
result = 31 * result + config.hashCode()
result = 31 * result + initialOffset.hashCode()
result = 31 * result + decayAnimationSpec.hashCode()
return result
}
companion object {
val Saver = Saver<CircularListStateImpl, List<Any>>(
save = { listOf(it.verticalOffset) },
restore = {
CircularListStateImpl(it[0] as Float)
}
)
}
}
#Composable
fun rememberCircularListState(): CircularListState {
val state = rememberSaveable(saver = CircularListStateImpl.Saver) {
CircularListStateImpl()
}
return state
}
#Composable
fun CircularList(
visibleItems: Int,
modifier: Modifier = Modifier,
state: CircularListState = rememberCircularListState(),
circularFraction: Float = 1f,
overshootItems: Int = 3,
content: #Composable () -> Unit,
) {
check(visibleItems > 0) { "Visible items must be positive" }
check(circularFraction > 0f) { "Circular fraction must be positive" }
Layout(
modifier = modifier.clipToBounds().drag(state),
content = content,
) { measurables, constraints ->
val itemHeight = constraints.maxHeight / visibleItems
val itemConstraints = Constraints.fixed(width = constraints.maxWidth, height = itemHeight)
val placeables = measurables.map { measurable -> measurable.measure(itemConstraints) }
state.setup(
CircularListConfig(
contentHeight = constraints.maxHeight.toFloat(),
numItems = placeables.size,
visibleItems = visibleItems,
circularFraction = circularFraction,
overshootItems = overshootItems,
)
)
layout(
width = constraints.maxWidth,
height = constraints.maxHeight,
) {
for (i in state.firstVisibleItem..state.lastVisibleItem) {
placeables[i].placeRelative(state.offsetFor(i))
}
}
}
}
private fun Modifier.drag(
state: CircularListState,
) = pointerInput(Unit) {
val decay = splineBasedDecay<Float>(this)
coroutineScope {
while (true) {
val pointerId = awaitPointerEventScope { awaitFirstDown().id }
state.stop()
val tracker = VelocityTracker()
awaitPointerEventScope {
verticalDrag(pointerId) { change ->
val verticalDragOffset = state.verticalOffset + change.positionChange().y
launch {
state.snapTo(verticalDragOffset)
}
tracker.addPosition(change.uptimeMillis, change.position)
change.consumePositionChange()
}
}
val velocity = tracker.calculateVelocity().y
val targetValue = decay.calculateTargetValue(state.verticalOffset, velocity)
launch {
state.decayTo(velocity, targetValue)
}
}
}
}
Android graphview load widget using androidplot library in build gradle
implementation "com.androidplot:androidplot-core:1.5.7"
Dynamicaally create chart using below code getresponse based from network call.
In class file my sample code...
val plot = XYPlot(context, "")
val h = context.resources.getDimension(R.dimen.sample_widget_height).toInt()
val w = context.resources.getDimension(R.dimen.sample_widget_width).toInt()
plot.graph.setMargins(100f, 0f, 0f, 16f)
plot.graph.setPadding(0f, 0f, 0f, 0f)
plot.graph.gridInsets.left = 100f
plot.graph.gridInsets.top = 0f
plot.graph.gridInsets.right = 0f
plot.graph.gridInsets.bottom = 40f
plot.legend.textPaint.textSize = 1f
plot.linesPerRangeLabel = 8
plot.linesPerDomainLabel = 8
plot.graph.setSize(Size.FILL)
plot.measure(w, h);
plot.layout(0, 0, w, h);
plot.graph.position(0f, HorizontalPositioning.ABSOLUTE_FROM_LEFT, 0f,
VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.LEFT_TOP)
val series1Numbers = mutableListOf<Int>()
val series2Numbers = mutableListOf<Int>()
val xLabels = mutableListOf<String>()
for (i in 0 until model.Details.Items.size) {
var item = model.Details.Items[i]
series1Numbers.add(item.TotalScore)
series2Numbers.add(0)
xLabels.add(item.Date)
}
val series1 = SimpleXYSeries(series1Numbers, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series1")
val series2 = SimpleXYSeries(series2Numbers, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series1")
val series1Format = LineAndPointFormatter(context, R.xml.line_point_formatter_with_labels)
val series2Format = LineAndPointFormatter(context,
R.xml.line_point_formatter_with_labels_2)
plot.graph.setLineLabelEdges(XYGraphWidget.Edge.LEFT, XYGraphWidget.Edge.BOTTOM)
plot.setRangeBoundaries(-110, 110, BoundaryMode.FIXED)
plot.graph.getLineLabelStyle(XYGraphWidget.Edge.LEFT).format = object : Format() {
override fun format(obj: Any, toAppendTo:
StringBuffer, pos: FieldPosition): StringBuffer {
val i = Math.round((obj as Number).toFloat())
L.m("widget y axos label value ", i.toString())
plot.graph.setLineLabelRenderer(XYGraphWidget.Edge.LEFT, MyLineLabelRenderer())
return if (i > 50) {
return toAppendTo.append(context.getString(R.string.str_excellent))
} else if (i > 25 && i <= 50) {
return toAppendTo.append(context.getString(R.string.str_very_good))
} else if (i > 0 && i <= 25) {
return toAppendTo.append(context.getString(R.string.str_good))
} else if (i == 0) {
return toAppendTo.append(context.getString(R.string.str_neutral))
} else if (i < 0 && i >= -25) {
return toAppendTo.append(context.getString(R.string.str_not_good))
} else if (i < -25 && i >= -50) {
return toAppendTo.append(context.getString(R.string.str_be_aware))
} else if (i < -50) {
return toAppendTo.append(context.getString(R.string.str_time_out))
} else {
return toAppendTo.append(context.getString(R.string.str_neutral))
}
}
override fun parseObject(source: String, pos: ParsePosition): Any {
// unused
return ""
}
}
plot.legend.setVisible(false)
plot.getGraph().linesPerDomainLabel = 5
plot.getGraph().linesPerRangeLabel = 5
plot.graph.getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).format = object : Format() {
override fun format(obj: Any, #NonNull toAppendTo:
StringBuffer, #NonNull pos: FieldPosition): StringBuffer {
val i = Math.round((obj as Number).toFloat())
val displayFormat = SimpleDateFormat(prefs.selectedTimeFormat, Locale.US)
val originalFormat = SimpleDateFormat(Constants.DATE_yyyyMMddHHmmss, Locale.US)
var displayTime = ""
if (prefs.selectedTimeFormat.equals(Constants.TWENTYFOUR_HOUR_FORMAT))
displayTime = displayFormat.format(originalFormat.parse(xLabels.get(i)))
else {
displayTime = displayFormat.format(originalFormat.parse(xLabels.get(i)))
displayTime.replace("AM", " am")
displayTime.replace("PM", " pm")
}
plot.graph.setLineLabelRenderer(XYGraphWidget.Edge.BOTTOM, MyLineLabelRenderer())
return toAppendTo.append(displayTime)
}
override fun parseObject(source: String, #NonNull pos: ParsePosition): Any {
return ""
}
}
plot.addSeries(series1, series1Format)
plot.addSeries(series2, series2Format)
val bitmap: Bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
plot.draw(Canvas(bitmap))
remoteViews!!.setImageViewBitmap(R.id.img_graph_view, bitmap)
appWidgetManager!!.updateAppWidget(appWidgetId, remoteViews)
I am tried to reduce bottom label shown text size is not working it's overlapping one another
How to reduce bottom label text size?
setTextsize used below code
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.LEFT).getPaint().textSize = 16f
I have to make following Ui, In which when I play this small circle will move in an arc, when I stop it will stop. I can also set the timing of rotation of the small blue circle.
Till now I have implemented the following code: this gives me an arc of the circle, but I am not able to rotate the smaller circler over the bigger one.
public class CustoCustomProgressBar : View{
private var path : Path ? =null
private var mPrimaryPaint: Paint? = null
private var mSecondaryPaint: Paint? = null
private var mBackgroundPaint: Paint? = null
private var mTextPaint: TextPaint? = null
private var mProgressDrawable : Drawable ? = null
private var mRectF: RectF? = null
private var mDrawText = false
private var mTextColor = 0
private var mSecondaryProgressColor = 0
private var mPrimaryProgressColor = 0
private var mBackgroundColor = 0
private var mStrokeWidth = 0
private var mProgress = 0
var secodaryProgress = 0
private set
private var firstArcprogress = 0
private var secondArcProgress = 0
private var thirdArcProgress = 0
private var mFristArcCapSize = 0
private var mSecondArcCapSize = 0
private var mThirdArcCapSize = 0
private var isFristCapVisible = false
private var isSecondCapVisible = false
private var isThirdCapVisible = false
private var capColor = 0
private var mPrimaryCapSize = 0
private var mSecondaryCapSize = 0
var isPrimaryCapVisible = false
var isSecondaryCapVisible = false
private var x = 0
private var y = 0
private var mWidth = 0
private var mHeight = 0
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
fun init(context: Context, attrs: AttributeSet?) {
val a: TypedArray
a = if (attrs != null) {
context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CustomProgressBar,
0, 0
)
} else {
throw IllegalArgumentException("Must have to pass the attributes")
}
try {
mProgressDrawable = resources.getDrawable(R.drawable.test)
mDrawText = a.getBoolean(R.styleable.CustomProgressBar_showProgressText, false)
mBackgroundColor =
a.getColor(
R.styleable.CustomProgressBar_backgroundColor,
resources.getColor(R.color.white)
)
mPrimaryProgressColor =
a.getColor(
R.styleable.CustomProgressBar_progressColor,
resources.getColor(R.color.white)
)
mSecondaryProgressColor =
a.getColor(
R.styleable.CustomProgressBar_secondaryProgressColor,
resources.getColor(R.color.black)
)
capColor =
a.getColor(
R.styleable.CustomProgressBar_capColor,
resources.getColor(R.color.color_9bc6e6_mind)
)
firstArcprogress =a.getInt(R.styleable.CustomProgressBar_firstArcProgress, 0)
mProgress = a.getInt(R.styleable.CustomProgressBar_progress, 0)
secodaryProgress = a.getInt(R.styleable.CustomProgressBar_secondaryProgress, 0)
mStrokeWidth = a.getDimensionPixelSize(R.styleable.CustomProgressBar_strokeWidth, 10)
mTextColor = a.getColor(
R.styleable.CustomProgressBar_textColor,
resources.getColor(R.color.black)
)
mPrimaryCapSize = a.getInt(R.styleable.CustomProgressBar_primaryCapSize, 20)
mSecondaryCapSize = a.getInt(R.styleable.CustomProgressBar_secodaryCapSize, 20)
isPrimaryCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_primaryCapVisibility, true)
isSecondaryCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_secodaryCapVisibility, true)
isFristCapVisible = a.getBoolean(R.styleable.CustomProgressBar_firstCapVisibility, true)
isSecondCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_secodaryCapVisibility, false)
isThirdCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_thirdCapVisibility, false)
} finally {
a.recycle()
}
mBackgroundPaint = Paint()
mBackgroundPaint!!.setAntiAlias(true)
mBackgroundPaint!!.setStyle(Paint.Style.STROKE)
mBackgroundPaint!!.setStrokeWidth(mStrokeWidth.toFloat())
mBackgroundPaint!!.setColor(mBackgroundColor)
mPrimaryPaint = Paint()
mPrimaryPaint!!.setAntiAlias(true)
mPrimaryPaint!!.setStyle(Paint.Style.STROKE)
mPrimaryPaint!!.setStrokeWidth(mStrokeWidth.toFloat())
mPrimaryPaint!!.setColor(capColor)
mSecondaryPaint = Paint()
mSecondaryPaint!!.setAntiAlias(true)
mSecondaryPaint!!.setStyle(Paint.Style.STROKE)
mSecondaryPaint!!.setStrokeWidth((mStrokeWidth - 2).toFloat())
mSecondaryPaint!!.setColor(mSecondaryProgressColor)
mTextPaint = TextPaint()
mTextPaint!!.color = mTextColor
mRectF = RectF()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mRectF!![paddingLeft.toFloat(), paddingTop.toFloat(), (w - paddingRight).toFloat()] =
(h - paddingBottom).toFloat()
mTextPaint!!.textSize = (w / 5).toFloat()
x = w / 2 - (mTextPaint!!.measureText("$mProgress%") / 2).toInt()
y = (h / 2 - (mTextPaint!!.descent() + mTextPaint!!.ascent()) / 2).toInt()
mWidth = w
mHeight = h
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mPrimaryPaint?.setStyle(Paint.Style.STROKE)
mSecondaryPaint?.setStyle(Paint.Style.STROKE)
// for drawing a full progress .. The background circle
mRectF?.let {
mBackgroundPaint?.let { it1 -> canvas.drawArc(it, 270F, 180F, false, it1) } }
path = Path()
path?.arcTo(mRectF,270F,180F,true)
mRectF?.let {
mBackgroundPaint?.let { it1 ->
canvas.drawArc(it, 95F, 80F, false, it1)
}
}
mRectF?.let {
mBackgroundPaint?.let { it1 ->
canvas.drawArc(it, 180F, 80F, false, it1)
}
}
// // for drawing a secondary progress circle
// val secondarySwipeangle = secodaryProgress * 360 / 100
// mRectF?.let { mSecondaryPaint?.let { it1 ->
// canvas.drawArc(it, 270F, secondarySwipeangle.toFloat(), false,
// it1
// )
// } }
//
val firstArcProgresSwipeAngle = firstArcprogress * 180 / 100
// mRectF?.let {
// mPrimaryPaint?.let { it1 ->
// canvas.drawArc(
// it, 270F, firstArcProgresSwipeAngle.toFloat(), false,
// it1
// )
// }
// }
// // for drawing a main progress circle
// val primarySwipeangle = mProgress * 360 / 100
// mRectF?.let { mPrimaryPaint?.let { it1 ->
// canvas.drawArc(it, 270F, primarySwipeangle.toFloat(), false,
// it1
// )
// } }
// // for cap of secondary progress
// val r = (height - paddingLeft * 2) / 2 // Calculated from canvas width
// var trad = (secondarySwipeangle - 90) * (Math.PI / 180.0) // = 5.1051
// var x = (r * Math.cos(trad)).toInt()
// var y = (r * Math.sin(trad)).toInt()
// mSecondaryPaint?.setStyle(Paint.Style.FILL)
// if (isSecondaryCapVisible) mSecondaryPaint?.let {
// canvas.drawCircle(
// (x + mWidth / 2).toFloat(),
// (y + mHeight / 2).toFloat(),
// mSecondaryCapSize.toFloat(),
// it
// )
// }
val r = (height - paddingLeft * 2) / 2
// for cap of primary progress
var trad = (firstArcProgresSwipeAngle - 90) * (Math.PI / 180.0) // = 5.1051
x = (r * Math.cos(trad)).toInt()
y = (r * Math.sin(trad)).toInt()
mPrimaryPaint?.setStyle(Paint.Style.FILL)
if (isPrimaryCapVisible) mPrimaryPaint?.let {
canvas.drawCircle(
(x + mWidth / 2).toFloat(),
(y + mHeight / 2).toFloat(),
mPrimaryCapSize.toFloat(),
it
)
}
if (mDrawText) mTextPaint?.let {
canvas.drawText(
"$mProgress%", x.toFloat(), y.toFloat(),
it
)
}
}
fun setDrawText(mDrawText: Boolean) {
this.mDrawText = mDrawText
invalidate()
}
override fun setBackgroundColor(mBackgroundColor: Int) {
this.mBackgroundColor = mBackgroundColor
mBackgroundPaint?.setColor(mBackgroundColor)
invalidate()
}
fun setStrokeWidth(mStrokeWidth: Int) {
this.mStrokeWidth = mStrokeWidth
invalidate()
}
fun setSecondaryProgress(mSecondaryProgress: Int) {
secodaryProgress = mSecondaryProgress
invalidate()
}
fun setTextColor(mTextColor: Int) {
this.mTextColor = mTextColor
mTextPaint!!.color = mTextColor
invalidate()
}
var secondaryProgressColor: Int
get() = mSecondaryProgressColor
set(mSecondaryProgressColor) {
this.mSecondaryProgressColor = mSecondaryProgressColor
mSecondaryPaint?.setColor(mSecondaryProgressColor)
invalidate()
}
var primaryProgressColor: Int
get() = mPrimaryProgressColor
set(mPrimaryProgressColor) {
this.mPrimaryProgressColor = mPrimaryProgressColor
mPrimaryPaint?.setColor(mPrimaryProgressColor)
invalidate()
}
var progress: Int
get() = mProgress
set(mProgress) {
while (this.mProgress <= mProgress) {
postInvalidateDelayed(150)
this.mProgress++
}
}
fun getBackgroundColor(): Int {
return mBackgroundColor
}
var primaryCapSize: Int
get() = mPrimaryCapSize
set(mPrimaryCapSize) {
this.mPrimaryCapSize = mPrimaryCapSize
invalidate()
}
var secondaryCapSize: Int
get() = mSecondaryCapSize
set(mSecondaryCapSize) {
this.mSecondaryCapSize = mSecondaryCapSize
invalidate()
}
var arcprogress: Int
get() = firstArcprogress
set(firstArcprogress) {
while (this.firstArcprogress <= firstArcprogress) {
postInvalidateDelayed(150)
this.firstArcprogress = firstArcprogress
}
}
fun getPath(): Path? {
return path
}
fun getXCOORDINTE(): Float{
return x.toFloat()
}
fun getYCoordinate(): Float{
return y.toFloat()
}
}