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
Related
I'm using edge-to-edge in my app. I want to add info window (ViewStub) at the top of the screen. Also i need to apply margin to this window to avoid it drawing on system top panel. I'm using this code:
fun View.applyTopInsetAsMargin() {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.top
}
windowInsets
}
}
It's working fine but it's overriding margin to just inset size. In my viewstub i'm setting topMagin to 8 dp because i need to lay it down from status bar and add extra 8 dp margin.
The problem is: when i'm using this extension my 8 dp margin getting overrided so viewstub is only getting top inset size. How can i presave my extra margin and apply it to my view?
val popupBinding = DemoPopupWindowBinding.bind(binding.demoPopupStub.inflate())
popupBinding.root.applyTopInsetAsMargin()
<ViewStub
android:id="#+id/demo_popup_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/horizontal_margin_16"
android:layout_marginTop="#dimen/vertical_margin_8"
android:layout_marginEnd="#dimen/horizontal_margin_16"
android:inflatedId="#+id/demo_popup_window"
android:layout="#layout/demo_popup_window" />
viewstub layout:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/demo_scheme_popup_background"
android:padding="#dimen/padding_8">
...
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.
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"/>
I am working on an Android 4+ app which uses a quite simply layout: Multiple views are stacked using a LinearLayout within a ScrollView
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="HardcodedText,UseCompoundDrawables,UselessParent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
<!-- Top Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
<Button... />
</LinearLayout>
<!-- Hidden Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
... Some Content ...
</LinearLayout>
<!-- Bottom Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
... Some Content ...
</LinearLayout>
</LinearLayout>
</ScrollView>
The HiddenContainer should not be visible when the layout it created. Thus in the beginning the BottomContainer is directly beneath the TopContainer. A click on the Button within the TopContainer toggles the visibility of the HiddenContainer.
Doing this with hiddenContainer.setVisibility(hiddenContainer.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE) works find and without any problem. However it does not look good when the view suddenly appears or disappears. Instead I would like to animate the change.
I was surprised that I was not able to find an easy solution for this:
Using android:animateLayoutChanges="true" does work, however I am not able to control the animation.
While using a ValueAnimator to change hiddenContainer.setScaleY(...) gives me control over the animation setScaleY(0) makes the container invisible without reducing the space it occupies within the layout.
Using the ValueAnimator to change hiddenContainer.setHeight(...) might work, however I don't want to use a fixed height value when showing the container (e.g. hiddenContainer.setHeight(300)) but the height which is determined by the containers content.
So, how to solve this?
For animate your changes of layout (alpha, visibility, height, etc) you can use TransitionManager. For example: I have three static methods and use them when I want to animate layout changes:
public static final int DURATION = 200;
public static void beginAuto(ViewGroup viewGroup) {
AutoTransition transition = new AutoTransition();
transition.setDuration(DURATION);
transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
TransitionManager.beginDelayedTransition(viewGroup, transition);
}
public static void beginFade(ViewGroup viewGroup) {
Fade transition = new Fade();
transition.setDuration(DURATION);
TransitionManager.beginDelayedTransition(viewGroup, transition);
}
public static void beginChangeBounds(ViewGroup viewGroup) {
ChangeBounds transition = new ChangeBounds();
transition.setDuration(DURATION);
TransitionManager.beginDelayedTransition(viewGroup, transition);
}
And when you want to animate layout changes you can just call one of this methods before layout changings:
beginAuto(hiddenContainerParentLayout);
hiddenContainer.setVisibility(hiddenContainer.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE)
Let's say that my Bottom Sheet has lines of widgets like the following. If I want to show only the first two lines (i.e., the first two LinearLayouts) initially, but not the rest of the widgets below. I do not want those to be seen initially. How can I set the correct peek height? Hard-coding app:behavior_peekHeight probably would not work, so I would need to set it programatically, but how to calculate the height?
Or is there a more recommended way to get the same result? I mean, if I test Google Maps, long pressing a location first shows only the title part as the bottom sheet, but when I try to scroll up the bottom sheet, it feels as if the title part (which might not have been a real bottom sheet) is replaced by a real bottom sheet that contains all the elements. If my explanation is not enough, please try Google Maps yourself.
<android.support.v4.widget.NestedScrollView
android:id="#+id/bottom_sheet"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<android.support.v7.widget.AppCompatSpinner/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<TextView/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<TextView/>
</LinearLayout>
<android.support.v7.widget.RecyclerView/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
I would solve this by using a ViewTreeObserver.OnGlobalLayoutListener to wait for your bottom sheet to be laid out, and then calling BottomSheetBehavior.setPeekHeight() with the y-coordinate of the first view you don't want to see.
private BottomSheetBehavior<View> behavior;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View bottomSheet = findViewById(R.id.bottomSheet);
behavior = BottomSheetBehavior.from(bottomSheet);
final LinearLayout inner = findViewById(R.id.inner);
inner.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
inner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
View hidden = inner.getChildAt(2);
behavior.setPeekHeight(hidden.getTop());
}
});
}
In this case, my bottom sheet is a NestedScrollView holding a LinearLayout that holds many TextViews. By setting the peek height to be the top of the third TextView (obtained by getChildAt(2)), my bottom sheet winds up showing exactly two TextViews while collapsed.
Customized #Ben P.'s answer to target a view id as a reference of the peekHeight and made a function:
/**
* Gets the bottom part of the target view and sets it as the peek height of the specified #{BottomSheetBehavior}
*
* #param layout - layout of the bottom sheet.
* #param targetViewId - id of the target view. Must be a view inside the 'layout' param.
* #param behavior - bottom sheet behavior recipient.
*/
private fun <T : ViewGroup> getViewBottomHeight(layout: ViewGroup,
targetViewId: Int,
behavior: BottomSheetBehavior<T>) {
layout.apply {
viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
behavior.peekHeight = findViewById<View>(targetViewId).bottom
}
})
}
}
In our use case, we needed to target the bottom part of the view, so we set it that way. It can be adjusted depending on the use-case.
That's smart!
My problem was trying to getTop() or getHeight() at wrong timing, it returns 0 if the view is not ready.
And yes, use viewTreeObserver to avoid that.
This is actually no different with #Ben P.'s previous answer, just a kotlin version:
class MyBottomSheetDialog() : BottomSheetDialogFragment(){
private val binding by viewBinding(SomeLayoutViewBinding::bind)
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(this.dialog as BottomSheetDialog).behavior.let { behavior ->
/* Set "pivotView" as interested target and make it the pivot of peek */
binding.pivotView.viewTreeObserver.addOnGlobalLayoutListener(object :
OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.pivotView.viewTreeObserver.removeOnGlobalLayoutListener(this)
behavior.peekHeight = binding.pivotView.top
}
})
}
}
}