Setting limits for randomly generated positions for button - android

I'm trying to make a very simple app where you have a timer that counts down from 30 seconds and a button on the screen, and each time the button is clicked it "moves" (its x and y values are changed). I haven't implemented the timer part of the app yet since in the version I currently have the button sometimes "disappears". My guess is that it's being moved to a position out of the bounds of the device's screen and I can't figure out how to set the limits so the random x and y values of the button don't surpass the size of the screen so the button is always visible and therefore clickable as well.
My current MainActivity.kt code:
package com.example.button_chaser
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.DisplayMetrics
import android.widget.Button
import android.widget.RelativeLayout
import android.widget.TextView
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
lateinit var button: Button
lateinit var text: TextView
var score = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.button)
var text: TextView = findViewById<TextView>(R.id.score)
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
var width = displayMetrics.widthPixels
var height = displayMetrics.heightPixels
println(width)
println(height)
button.setOnClickListener{
score += 1
text.text = "Score: $score"
button.x = Random.nextInt(width).toFloat()
button.y = Random.nextInt(height).toFloat()
}
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/my_relativeLayout"
tools:context=".MainActivity"
android:background="#color/grey"
>
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.499" />
<TextView
android:id="#+id/score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Score"
android:padding="16dp"
android:layout_marginTop="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textStyle="bold"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Display metrics gives you the size of the whole screen, including the status bar, bottom system bar, etc. Depending on which settings the user has turned on, you may or may not have to try to find the heights of the system bars and selectively subtract them. This is very complicated and error prone.
You could get the height and width of your view, but not until after the scene has already been laid out, which hasn't happened yet in onCreate(). Even if you do that, you also have to keep in mind the size of the button itself so you don't pick a position that crops off its right or bottom edge. So that is also messy.
Instead, I would put the Button in a ConstraintLayout and constrain all four of its edges to the parent's edges. This will center it. Then you can modify verticalBias and horizontalBias to put it at a random position that fits in the view.
verticalBias and horizontalBias are a number between 0 and 1 that shifts a view between the extremes of its relevant constraints, so you can simply choose a random number between 0 and 1 to get a random position. You can use Random.nextFloat() to get a random number from 0 to 1.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/myButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
private fun randomizeButtonPosition() {
button.layoutParams = (button.layoutParams as ConstraintLayout.LayoutParams).apply {
horizontalBias = Random.nextFloat()
verticalBias = Random.nextFloat()
}
}
Note we are only modifying the existing LayoutParams instance that is set on the button, but we also have to reassign it back to the button anyway to make the ConstraintLayout aware that it needs to recompute the position.

Related

overlay colors on the view

I am facing a specific issue of view background overlay. I did simple example project to show my problem
i have 2 view with the same size. Also both view have background - GradientDrawable with corner radius.
xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="100dp">
<View
android:id="#+id/view_1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View
android:id="#+id/view_2"
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:layout_height="match_parent"/>
</FrameLayout>
And my mainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val view1 = findViewById<View>(R.id.view_1)
val view2 = findViewById<View>(R.id.view_2)
val bg1 = GradientDrawable().apply {
setColor(Color.BLUE)
cornerRadius = 20.toPxF()
}
val bg2 = GradientDrawable().apply {
setColor(Color.RED)
cornerRadius = 20.toPxF()
}
view1.background = bg1
view2.background = bg2
}
}
what i see
full screen full screen
also there are transitions between colors in rounded corners
top
bottom
Actually I don't want to see color changes (colour overlays) in the rounded corners.
How can I achieve this?
UPDATE: i added stroke for second gradient drawable
this and i see that border color smoothed out.
i also try to add stroke for second (red color) view and positioned it directly above the first (blue color) view
click
I want something like that click
bottom bounds have the same coordinates, both views have the same rounding
I could elaborate on this topic for hours, but in short: you want this effect. well, at least on top part of your view. and bottom doesn't look well, but you can add layout_marginBottom to it, right? homework: read about antialiasing

Android: Switch Height and Width when View change orientation

I do use PhotoView library to ZoomIn-Out.
now when user click a button i prepared to rotate the PhotoView, Height and Width would rotate.
so width which is smaller than the screen Height.
and this result in i can't get the full screen zoom like usual before Rotating the Imageview.
so any solution to make the new width or height after rotation to take full screen.
Example : this is Zoomed Image it doesn't zoom in the entire screen like before the rotation
I had to work with the same type of feature on my project and here was my design and implementation for it.
Basic XML design for photo view
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.github.chrisbanes.photoview.PhotoView
android:id="#+id/image_main"
android:layout_width="0dp"
android:scaleType="centerCrop"
android:layout_height="400dp"
android:src="#drawable/gull_portrait_ca_usa"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/image_rotate"
android:src="#android:drawable/ic_menu_rotate"
android:layout_margin="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="40dp"
android:layout_height="40dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Then I just update the orientation onClick event.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.imageRotate.setOnClickListener {
binding.imageMain.rotation = binding.imageMain.rotation+90
}
}
}
Output
You'll need to create 2 xml layout. The portrait (the one you have already created inside res > layout folder) and the landscape (inside res > layout-land).
When you rotate the phone, fragment will load the xml file inside layout-land folder.
You can find a tutorial here: https://www.geeksforgeeks.org/how-to-create-landscape-layout-in-android-studio/
And more info here: https://developer.android.com/guide/topics/resources/providing-resources
If Image will be rotated, it will adjust in the screen as per his aspect ratio. If you want to fit it to full screen, the image will starched that will not look good. If still you want to do it, simply use the Image as 'background' at the place of 'Image Resources' and keep the view length and width as 'match parent'.
See below sample view:
<ImageView
android:id="#+id/mainImageID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/yourImage"/>

Unable to scroll to item in RecyclerView that is inside NestedScrollView

I'm trying to programmatically scroll to a particular item within my RecyclerView which is nested within a NestedScrollView.
The Problem
The NestedScrollView scrolls to the complete bottom rather than the desired item.
Note: It works correctly when desired item is the 2nd item, probably since that item is visible in the screen.
What I've tried
I've searched through a dozen solutions from StackOverFlow and came up with the function below.
I've tried:
binding.variantsRecyclerView.getChildAt()
binding.variantsRecyclerView.findViewWithTag()
binding.variantsRecyclerView.findViewHolderForAdapterPosition()
All these do return the correct item, (I know since the edit text within that item is focused as coded) however, the NestedScrollView does not scroll correctly to that item. It is almost always scrolling to the bottom. Sometimes however it scrolls to somewhere in between the required item instead of it's start. The only time this works is when the item is either the 1st or 2nd item. (As stated before)
private fun scrollToPosition(position: Int) {
if (position in 0 until variantsAdapter.itemCount) {
val view = binding.variantsRecyclerView.findViewWithTag<ConstraintLayout>("pos_$position")
if (view != null) {
val height = binding.nestedScrollView.height
val target = view.y.toInt()
binding.nestedScrollView.postDelayed({
binding.nestedScrollView.smoothScrollTo(0, target)
view.requestFocus()
}, 200)
}
}
}
My XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#eff1f4">
<LinearLayout>...</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constrainedHeight="true"
app:layout_constraintVertical_bias="0"
app:layout_constraintBottom_toTopOf="#+id/btnNextLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbarLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/ui_10_dp"
android:layout_marginEnd="#dimen/ui_10_dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/variantsRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingTop="12dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="#layout/sell_variant_row_item" />
<androidx.constraintlayout.widget.ConstraintLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout>...</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
My Understanding
After debugging, I found out that the NestedScrollView height is lesser than the y co-ordinate of the desired item. Hence it scrolls to the bottom of the view instead of the desired item. My understanding could be completely wrong and if so, please correct me.
I resolved this with a really simple fix.
private fun scrollToPosition(position: Int) {
if (position in 0 until variantsAdapter.itemCount) {
val view = binding.variantsRecyclerView.getChildAt(position)
if (view != null) {
val target = binding.variantsRecyclerView.top + view.top
binding.nestedScrollView.scrollY = target
}
}
}
All I wanted was to get the desired item within the RecyclerView to the top of the screen.

Popup menu uses original size of Image as anchor, instead of actual size

I am having an image view displaying a nice image and opening a popup-menu on tap.
The images I use have different sizes in original, so the imageView has a static width.
However when I anchor the pop-up-menu at the imageView, it uses the original sized image as anchor, which results in very weird views. (Screenshots appended)
Of course I could size all images on the same size, but since the images are having sizes in px and not dp, it would lead to problems on screens with a different screen-resolution.
Is this an error in my code or a bug of the pop-up menu? Wanted to file it as bug, but it´s pretty much impossible to find the place to do so either.
Here´s my xml for the imageView (image view having a default icon):
<ImageView
android:id="#+id/languageFlag"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/activity_vertical_margin"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="#id/searchView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/searchView"
app:layout_constraintTop_toTopOf="#+id/searchView"
app:srcCompat="#drawable/ic_baseline_casino_24" />
Here´s my xml for the whole fragment (Just in case this matters):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="bi.deutsch_kirundi_app.fragments.DictionaryFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/lightgrey">
<ImageView
android:id="#+id/languageFlag"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/activity_vertical_margin"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="#id/searchView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/searchView"
app:layout_constraintTop_toTopOf="#+id/searchView"
app:srcCompat="#drawable/ic_baseline_casino_24" />
<SearchView
android:id="#+id/searchView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_marginLeft="#dimen/activity_vertical_margin"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:background="#color/white"
android:iconifiedByDefault="false"
app:layout_constraintEnd_toStartOf="#id/languageFlag"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
</SearchView>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/result_fragment"
android:name="bi.deutsch_kirundi_app.fragments.dictionary.AllVocabularyFragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="#dimen/activity_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/searchView"
tools:layout="#layout/fragment_all_vocabulary" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
In the "onCreateView"-method of my fragment, I get the imageView, get the images ids and set the one for the corresponding language saved in SharedPreferences. (Logically)
languagePicker = view.findViewById(R.id.languageFlag)
val sharedPreferences = view.context.getSharedPreferences(App.APP_PREFERENCES, Context.MODE_PRIVATE)
val lastChosenTranslationDirection = sharedPreferences.getInt(App.TRANSLAT_KEY, 0)
val ids = view.context.resources.getStringArray(R.array.translation_direction_flags)
images = Array(ids.size) {
val imageId = resources.getIdentifier(ids[it], "drawable", activity?.packageName)
ContextCompat.getDrawable(view.context, imageId)
}
languagePicker.setImageDrawable(images[lastChosenTranslationDirection])
Then I am setting the onclick-listener on the imageView (languagePicker) the following:
languagePicker.setOnClickListener {
val dropDownMenu = PopupMenu(it.context, languagePicker)
val languages = it.resources.getStringArray(R.array.translation_directions)
for(index in languages.indices) {
dropDownMenu.menu.add(0, index, index,
languages[index])
dropDownMenu.show()
dropDownMenu.setOnMenuItemClickListener(this)
}
}
Seems fine to me, but maybe I am blinded. Is there a need to add some specific property to the imageview, so the size of the Drawable is also adapted?
EDIT: I don´t want the width of the PopUp-Menu to be changed. Rather I want the Popup-Menu to use the ImageView as anchor, not the originally sized image. As visible on the screenshot with the German Flag, it is pushed to the bottom instead of sticking to the ImageView.
The width of the PopupMenu item layout actually doesn't have anything to do with the size of the original images set on the ImageView. The item layouts for PopupMenus simply have a minimum default width, and I don't think you can change it.
I would suggest that you switch to using Spinner, PopupWindow, or ListPopupWindow. These allow you to set your own item layouts, so you can choose exactly how you want each item to look.
I believe the default item layout for a Spinner just has its width set to the widest item in the list, so that might be the easiest route:
https://developer.android.com/guide/topics/ui/controls/spinner

How to arrange elements inside a Row, where one element has infinite width?

I'm trying to translate the following layout to Compose:
As you can see, the text on the left can be long, but it's allowed to wrap - the important thing is that it leaves enough space for the text on the right to be rendered. Here's what it looks like in XML:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"
>
<TextView
android:id="#+id/stock_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
app:layout_constraintEnd_toStartOf="#+id/holding_quantity"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Vanguard Total Stock Market Index Fund ETF Shares"
/>
<TextView
android:id="#+id/holding_quantity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.AppCompat.Display1"
android:textColor="?attr/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="10"
/>
</merge>
I'm struggling to find the right Row configuration that would work:
#Composable fun HoldingView() {
Row {
val typography = MaterialTheme.typography
Text("Vanguard Total Stock Market Index Fund ETF Shares", style = typography.body1)
Spacer(Modifier.preferredWidth(16.dp))
Text("10", style = typography.h4, color = MaterialTheme.colors.secondary)
}
}
Without any extra modifiers, this simply pushes the text on the right out of bounds:
Things I've tried without any luck:
Playing with various Arrangement options, such as SpaceBetween
Assigning a Modifier.weight(1f) to the second Text
Neither of these had any effect on how the views are laid out. Is there a configuration that would work for this use case?
The solution is to use Modifier.weight(1f) (which is available on the RowScope within the Row{ ... }) on the Text that you want to fill available space after all the other elements are laid out (that is, in your case, the first one).
So your example would look like:
Row {
val typography = MaterialTheme.typography
Text(
"Vanguard Total Stock Market Index Fund ETF Shares",
style = typography.body1,
modifier = Modifier.weight(1f)
)
Spacer(Modifier.preferredWidth(16.dp))
Text("10", style = typography.h4, color = MaterialTheme.colors.secondary)
}
which renders as (poor cropping on my screenshot messing with the margins on the edges notwithstanding)

Categories

Resources