Haii developer, i have issue to implement view pager transformer like design below, in design its not try to scale the page, the page is still have same size, but the highlight item(in center) have y axis higher then other. This is my expected view result:
But this how i get with my current code:
Here is my code for transforming page:
val nextItemVisiblePx = resources.getDimension(R.dimen.viewpager_next_item_visible)
val currentItemHorizontalMarginPx =
resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
val pageTransformer = ViewPager2.PageTransformer { page: View, position: Float ->
page.translationX = -pageTranslationX * position
// Next line scales the item's height. You can remove it if you don't want this effect
page.scaleY = 1 - (0.25f * kotlin.math.abs(position))
// If you want a fading effect uncomment the next line:
page.alpha = 0.6f + (1 - kotlin.math.abs(position))
}
layoutBaseFollowingFeedsSuggestion.rvSuggestionFollowingFeedList.setPageTransformer(pageTransformer)
// The ItemDecoration gives the current (centered) item horizontal margin so that
// it doesn't occupy the whole screen width. Without it the items overlap
val itemDecoration = HorizontalMarginItemDecoration(
requireActivity(),
R.dimen.dimen_25dp
)
layoutBaseFollowingFeedsSuggestion.rvSuggestionFollowingFeedList.addItemDecoration(itemDecoration)
Thanks!
Related
I have a HorizontalPager (from Accompanist) with a currentPage focus effect. On the emulator, it works correctly and with the correct spacing that I intend, however, on a smartphone, it doesn't respect the itemSpacing and completely ignores it, overlapping the pager items on the right. Is there any way to make this pager always have the same spacing regardless of the device? Or, at least, that the item on the right does not overlap and that the main item overlaps the other two?
Below a few screenshots of what is happening and the behavior that I want:
Correct behavior (working on emulator):
Wrong behavior & problem (working on physical device):
Code:
HorizontalPager(
state = state,
count = items.count(),
contentPadding = PaddingValues(horizontal = 72.dp),
itemSpacing = 10.dp,
modifier = modifier.padding(top = 16.dp)
) { page ->
Card(
shape = RectangleShape,
modifier = Modifier
.graphicsLayer {
// Calculate the absolute offset for the current page from the
// scroll position. We use the absolute value which allows us to mirror
// any effects for both directions
val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
// We animate the scaleX + scaleY, between 85% and 100%
lerp(
start = 0.85f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale
}
// We animate the alpha, between 50% and 100%
alpha = lerp(
start = 0.6f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
)
}
.requiredWidth(284.dp)
.requiredHeight(168.dp)
//.coloredShadow(Shadow, 0.28f)
) {
// content
}
}
Any additional necessary code can be provided, just let me know.
You should be using how to support different screen sizes in android with jetpack compose, in which you can define dimensions for different configurations.
You can refer to this tutorial on the same
https://proandroiddev.com/supporting-different-screen-sizes-on-android-with-jetpack-compose-f215c13081bd
I am working on a pin entry screen and the pin digit orbs are being scaled once their total width becomes greater than the content space available. But the first and final orb are pushing out of the content space and I can't figure out why.
This is an exmaple of the pin orbs pushing out the edges:
Code:
padViewHolder.addView(
padView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
padView.setPadActionListener(object : PadView.PadActionListener {
override fun symbolPressed() {
val digit = ImageView(pinPadDigitContainer.context)
digit.setImageResource(R.drawable.ic_pin_digit_filled)
if (noSpace) {
noSpace = false
pinPadDigitContainer.addView(digit)
pinPadDigitContainer.resizeChildrenToFit()
} else {
val space = Space(context)
pinPadDigitContainer.addView(space)
pinPadDigitContainer.addView(digit)
pinPadDigitContainer.resizeChildrenToFit()
pinPadDigitContainer.invalidate()
}
}
private fun ViewGroup.resizeChildrenToFit() {
val numOfOrbs = (childCount - 1) / 2
val displayWidth = resources.displayMetrics.run { widthPixels / density }
val singleSpace = calculatePercentageSpace(displayWidth).toInt() // 7% of screen width
val widthOfOrb = resources.getDimensionPixelSize(R.dimen.ic_pin_digit_width) // 29dp
val dpInt = resources.getDimensionPixelSize(R.dimen.ic_pin_orb_space) // 3dp
val contentSpace = width - paddingLeft - paddingRight
var childrenWidth = numOfOrbs * widthOfOrb + (singleSpace * (numOfOrbs - 1))
if (childrenWidth <= contentSpace) {
for (i in 0 until childCount) {
calculateOrbAndSpaceWidth(i, widthOfOrb, singleSpace)
}
} else {
childrenWidth = remain
val smallOrb = contentSpace / numOfOrbs - 3
for (i in 0 until childCount) {
calculateOrbAndSpaceWidth(i, smallOrb, dpInt)
}
}
}
This is the part of the view i'm working with:
<LinearLayout
android:id="#+id/meta__display2_container"
android:layout_width="0dp"
android:minWidth="0dp"
android:layout_height="0dp"
android:minHeight="0dp"
android:gravity="center"
android:orientation="horizontal"
android:clipChildren="true"
app:layout_constraintDimensionRatio="5.5:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/meta__display1_s"
app:layout_constraintWidth_percent="0.56" />
You haven't posted the actual code where you calculate the scaled widths (calculateOrbAndSpaceWidth) but it looks like that's not being worked out correctly. Your code's a little hard for me to follow, so I can't really rework it, but can't you do something like this?
val orbWidth = // get default orb width
val spaceWidth = // get default space between orbs
val totalWidth = (orbWidth * orbCount) + (spaceWidth * (orbCount-1))
val availableWidth = // get width of view - padding
// Work out what scaling factor would make all the orbs fit the available space exactly.
// If they need to be scaled down (scale: less than 1.0) use that value, otherwise keep them
// at the current size (scale: 1.0)
val scaleFactor = minOf(1f, availableWidth / totalWidth.toFloat())
And then draw all your orbs with the scale factor applied to the orb dimensions, space widths, and any coordinate offsets. (I don't know how you're actually drawing them, but you're scaling them somehow!) When scaleFactor is 1.0 it'll draw everything as normal, when it's smaller it'll adjust all your measurements to make them fit.
Also make sure you're not mixing up dp and px measurements - you seem to be working with both. Personally I'd convert any of your dp values to pixels (which you're mostly doing) and just work with that. This part here:
val displayWidth = resources.displayMetrics.run { widthPixels / density }
Is taking the width of the window in pixels and converting it to dp. You only use it in this function call:
val singleSpace = calculatePercentageSpace(displayWidth).toInt()
so I can't see what you're doing with it - but you either that result to calculateOrbAndSpaceWidth, or dpInt (which is a px value), and you're getting expected results with one and unexpected with the other. So just make sure you're being consistent in there - might be fine, just pointing it out!
I would like to reduce the reduce bar code tracking window when using the google vision api. There are some answers here but they feel a bit outdated.
I'm using google's sample: https://github.com/googlesamples/mlkit/tree/master/android/vision-quickstart
Currently, I try to figure out if a barcode is inside my overlay box inside BarcodeScannerProcessor onSuccess callback:
override fun onSuccess(barcodes: List<Barcode>, graphicOverlay: GraphicOverlay) {
if(barcodes.isEmpty())
return;
for(barcode in barcodes) {
val center = Point(graphicOverlay.imageWidth / 2, graphicOverlay.imageHeight / 2)
val rectWidth = graphicOverlay.imageWidth * Settings.OverlayWidthFactor
val rectHeight = graphicOverlay.imageHeight * Settings.OverlayHeightFactor
val left = center.x - rectWidth / 2
val top = center.y - rectHeight / 2
val right = center.x + rectWidth / 2
val bottom = center.y + rectHeight / 2
val rect = Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
val contains = rect.contains(barcode.boundingBox!!)
val color = if(contains) Color.GREEN else Color.RED
graphicOverlay.add(BarcodeGraphic(graphicOverlay, barcode, "left: ${barcode.boundingBox!!.left}", color))
}
}
Y-wise it works perfectly, but the X values from barcode.boundingBox e.g. barcode.boundingBox.left seems to have an offset. Is it based on what's being calculated in GraphicOverlay?
I'm expecting the value below to be close to 0, but the offset is about 90 here:
Or perhaps it's more efficient to crop the image according to the box?
Actually the bounding box is correct. The trick is that the image aspect ratio doesn't match the viewport aspect ratio so the image is cropped horizontally. Try to open settings (a gear in the top right corner) and choose an appropriate resolution.
For example take a look at these two screenshots. On the first one the selected resolution (1080x1920) matches my phone resolution so the padding looks good (17px). On the second screenshot the aspect ratio is different (1.0 for 720x720 resolution) therefore the image is cropped and the padding looks incorrect.
So the offset should be transformed from image coordinates to the screen coordinates. Under the hood GraphicOverlay uses a matrix for this transformation. You can use the same matrix:
for(barcode in barcodes) {
barcode.boundingBox?.let { bbox ->
val offset = floatArrayOf(bbox.left.toFloat(), bbox.top.toFloat())
graphicOverlay.transformationMatrix.mapPoints(offset)
val leftOffset = offset[0]
val topOffset = offset[1]
...
}
}
The only thing is that the transformationMatrix is private, so you should add a getter to access it.
As you know, the preview size of the camera is configurable at the settings menu. This configurable size specifies the graphicOverlay dimensions.
On the other hand, the aspect ratio of the CameraSourcePreview (i.e. preview_view in activity_vision_live_preview.xml) which is shown on the screen, does not necessarily equal to the ratio of the graphicOverlay. Because depends on the size of the phone's screen and the height that the parent ConstraintLayout allows occupying.
So, in the preview, based on the difference between the aspect ratio of graphicOverlay and preview_view, some part of the graphicOverlay might not be shown horizontally or vertically.
There are some parameters inside GraphicOverlay that can help us to adjust the left and top of the barcode's boundingBox in such a way that they start from 0 in the visible area.
First of all, they should be accessible out of the GraphicOverlay class. So, it's just enough to write a getter method for them:
GraphicOverlay.java
public class GraphicOverlay extends View {
...
/**
* The factor of overlay View size to image size. Anything in the image coordinates need to be
* scaled by this amount to fit with the area of overlay View.
*/
public float getScaleFactor() {
return scaleFactor;
}
/**
* The number of vertical pixels needed to be cropped on each side to fit the image with the
* area of overlay View after scaling.
*/
public float getPostScaleHeightOffset() {
return postScaleHeightOffset;
}
/**
* The number of horizontal pixels needed to be cropped on each side to fit the image with the
* area of overlay View after scaling.
*/
public float getPostScaleWidthOffset() {
return postScaleWidthOffset;
}
}
Now, it is possible to calculate the left and top difference gap using these parameters like the following:
BarcodeScannerProcessor.kt
class BarcodeScannerProcessor(
context: Context
) : VisionProcessorBase<List<Barcode>>(context) {
...
override fun onSuccess(barcodes: List<Barcode>, graphicOverlay: GraphicOverlay) {
if (barcodes.isEmpty()) {
Log.v(MANUAL_TESTING_LOG, "No barcode has been detected")
}
val leftDiff = graphicOverlay.run { postScaleWidthOffset / scaleFactor }.toInt()
val topDiff = graphicOverlay.run { postScaleHeightOffset / scaleFactor }.toInt()
for (i in barcodes.indices) {
val barcode = barcodes[i]
val color = Color.RED
val text = "left: ${barcode.boundingBox!!.left - leftDiff} top: ${barcode.boundingBox!!.top - topDiff}"
graphicOverlay.add(MyBarcodeGraphic(graphicOverlay, barcode, text, color))
logExtrasForTesting(barcode)
}
}
...
}
Visual Result:
Here is the visual result of the output. As it's obvious in the pictures, the gap between both left & top of the barcode and the left and top of the visible area is started from 0. In the case of the left picture, the graphicOverlay is set to the size of 480x640 (aspect ratio ≈ 1.3334) and for the right one 360x640 (aspect ratio ≈ 1.7778). In both cases, on my phone, the CameraSourcePreview has a steady size of 1440x2056 pixels (aspect ratio ≈ 1.4278), so it means that the calculation truly reflected the position of the barcode in the visible area.
(note that the aspect ratio of the visible area in one experiment is lower than that of graphicOverlay, and in another experiment, greater: 1.3334 < 1.4278 < 1.7778. So, the left values and top values are adjusted respectively.)
I put an Imageview in a match_parent Framelayout, the Framelayout's visibility is GONE, then caculate the ImageView's coordinate and setX/Y then make the Framelayout VISIABLE. But the ImageView's layout position is weird!
The Code is simple:
override fun onVisibilityChanged(
changedView: View?,
visibility: Int) {
if(visibility == View.VISIBLE && mTargetView != null){
var location = intArrayOf(0,0)
mTargetView?.getLocationOnScreen(location)
var x = location[0] + mTargetView?.width!! / 2 - mIconWidth/2
var y = location[1] - mIconHeight / 2 - mStatusBarH
Log.d("lee","\nbefore x: ${mDelView.x}, y: ${mDelView.y}")
/*
* use this code will get the right result.
mLayoutParams.leftMargin = x
mLayoutParams.topMargin = y
*/
mDelView.x = x.toFloat()
mDelView.y = y.toFloat()
Log.d("lee","\nafter x: ${mDelView.x}, y: ${mDelView.y}")
}
super.onVisibilityChanged(changedView, visibility)
}
The log show the coordinate is correct:
lee: before x: 492.0, y: 332.0
after x: 492.0, y: 332.0
The layout position show the ImageView's layout position always at the left top corner. When use the picture bellow is using the LayoutParams margin and the layout position is right. It really confused me.
The layout position show the ImageView's layout position always at the left top corner
Because this is how FrameLayout works. You want to position container's childs by hand, use either RelativeLayout or write own container.
The View's layout bounds decided by the view's left, top, bottom, right position, relative to parent. The second phase of the layout mechanism will caculate these position.
/**
* Sets the visual x position of this view, in pixels. This is equivalent to setting the
* {#link #setTranslationX(float) translationX} property to be the difference between
* the x value passed in and the current {#link #getLeft() left} property.
*
* #param x The visual x position of this view, in pixels.
*/
public void setX(float x) {
setTranslationX(x - mLeft);
}
The setX source code show this function only change the the visual x position of this view, and the mLeft value not change. So setX/Y will not change the layout positon.
I'm using a BarChart in an app but sometimes when I re-draw it the bars width is not respected and I get inconsistent results (even though the amount of values in the X axis is always 100).
This is how I want it to look always:
but sometimes it looks like this:
Does anyone know why this happens and how can I force it to always look the same?
I think it has something to do with the range of values in the X axis, as the chart looks good when the range goes from 0 to 50 or similar, but it looks bad when the range is smaller.
I'm already setting a bar width value like this but it doesn't help:
barChartView?.data?.barWidth = 0.3f
This is the full chart configuration I'm using (this method runs only once when the Fragment is created):
private fun setUpChartView() {
barChartView?.setTouchEnabled(false)
val xAxis = barChartView?.xAxis
val yAxis = barChartView?.axisLeft
// Hide all parts of the chart that we don't want to show
barChartView?.legend?.isEnabled = false
barChartView?.description?.isEnabled = false
barChartView?.setDrawGridBackground(false)
xAxis?.setDrawGridLines(false)
xAxis?.setDrawAxisLine(false)
yAxis?.setDrawAxisLine(false)
barChartView?.axisRight?.isEnabled = false
// Show the Y axis grid lines as dashed
yAxis?.enableGridDashedLine(5f, 10f, 0f)
val barChartTextColor = getColor(R.color.chart_text)
val barChartTextSize = 12f
val barChartLabelsTypeface = Typeface.SANS_SERIF
// Show the X axis labels at the bottom and set the proper style
xAxis?.position = XAxis.XAxisPosition.BOTTOM
xAxis?.textColor = barChartTextColor
xAxis?.textSize = barChartTextSize
xAxis?.typeface = barChartLabelsTypeface
// Set the proper style for the Y axis labels
yAxis?.textSize = barChartTextSize
yAxis?.textColor = barChartTextColor
yAxis?.typeface = barChartLabelsTypeface
barChartView?.setNoDataText("")
val viewPortHandler = barChartView?.viewPortHandler
val axisTransformer = barChartView?.getTransformer(LEFT)
// Set a custom axises so that we can control the drawing of the labels and grid lines
barChartView?.setXAxisRenderer(CustomEntriesXAxisRenderer(viewPortHandler = viewPortHandler,
xAxis = xAxis,
transformer = axisTransformer))
barChartView?.rendererLeftYAxis = CustomEntriesYAxisRenderer(viewPortHandler = viewPortHandler,
yAxis = yAxis,
transformer = axisTransformer)
}
This is the code I use to draw the chart when I get new data:
private fun drawBarChart() {
// The data list has always 100 items
val entries = data.map { BarEntry(it.x, it.y) }
// Prepare the chart data set by adding the entries and configuring its style (colors, etc.)
val dataSet = BarDataSet(entries, null)
dataSet.setDrawValues(false)
dataSet.colors = colorsList
// Load data to chart
if (barChartView?.data == null) {
// If there is no data set yet, create a new data object and add it to the chart (this happens the first
// time we draw the chart after the Fragment was created, or after an empty data list was returned)
barChartView?.data = BarData(dataSet)
} else {
// If there is already data in the chart, remove the existing data set and add the new one
barChartView.data?.removeDataSet(0)
barChartView.data?.addDataSet(dataSet)
barChartView.notifyDataSetChanged()
}
// Set a custom width for the bars or the chart will draw them too wide
barChartView?.data?.barWidth = 0.3f
val xAxis = barChartView?.xAxis
val yAxis = barChartView?.axisLeft
// Set the min and max values for the Y axis or the chart lib will calculate them and add more values than
// we need
yAxis?.axisMinimum = 0f
yAxis?.axisMaximum = entries.last().y
// Set the entries that need to be drawn in the X axis, so the chart doesn't calculate them automatically
(barChartView?.rendererXAxis as CustomEntriesXAxisRenderer).entries = xAxisEntries
// Set the entries that need to be drawn in the Y axis, so the chart doesn't calculate them automatically
(barChartView?.rendererLeftYAxis as CustomEntriesYAxisRenderer).entries = yAxisEntries
// Add the label count to avoid the chart from automatically setting a range of labels instead of the ones we need,
// which prevents the axis value formatter below to set the correct labels
xAxis?.setLabelCount(xAxisEntries.size, true)
yAxis?.setLabelCount(yAxisEntries.size, true)
// Use a custom value formatter that sets only the needed labels on the axises
xAxis?.setValueFormatter { value, _ ->
// TODO
}
yAxis?.setValueFormatter { value, _ ->
// TODO
}
// Draw chart
barChartView?.invalidate()
}
And this is the implementation of the custom axis renderers I created:
class CustomEntriesXAxisRenderer(viewPortHandler: ViewPortHandler?, xAxis: XAxis?, transformer: Transformer?)
: XAxisRenderer(viewPortHandler, xAxis, transformer) {
/**
* Entries used to draw the grid lines and labels on the X axis.
*/
var entries: List<Float>? = null
override fun computeSize() {
entries?.forEachIndexed { i, value ->
mAxis.mEntries[i] = value
}
super.computeSize()
}
}
class CustomEntriesYAxisRenderer(viewPortHandler: ViewPortHandler?, yAxis: YAxis?, transformer: Transformer?)
: YAxisRenderer(viewPortHandler, yAxis, transformer) {
/**
* Entries used to draw the grid lines and labels on the Y axis.
*/
var entries: List<Float>? = null
override fun computeAxisValues(min: Float, max: Float) {
super.computeAxisValues(min, max)
entries?.forEachIndexed { i, value ->
mAxis.mEntries[i] = value
}
}
}
One of the MPAndroidChart devs said this is indeed a bug in the library and it's now in their to-do list. If anyone is having this issue you can follow the progress here.
In the meantime, after playing around with this for a while I found a (quite ugly) hack that prevents the chart from looking too bad:
barChartView?.data?.barWidth = when (entries.last().x) {
in 0..30 -> 0.1f
in 30..60 -> 0.2f
else -> 0.3f
}
You can adjust the values to what looks good for your data entries, but basically by setting a different bar width depending on the range of X values in the data set you can at least make this a bit better until the bug is fixed.