I'd like to make enter animation for activity using ConstraintSet, as it's shown in this video:
https://www.youtube.com/watch?v=OHcfs6rStRo.
The problem is I don't know in which lifecycle method to put code to make the transition visible to user.
In onCreate I'm calling:
setContentView(R.layout.layout_first_keyframe_detail);
topConstraintLayout = findViewById(R.id.top_constraint_layout);
constraintSet = new ConstraintSet();
constraintSet.clone(this, R.layout.layout_detail_top);
and then I'd like to call:
TransitionManager.beginDelayedTransition(topConstraintLayout);
constraintSet.applyTo(topConstraintLayout);
when activity is already visible. Unfortunately I don't find any lifecycle method to do this.
If you look at TransitionManager.beginDelayedTransiton() implementation, you will see that it starts with following check :
if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut())
Looking at isLaidOut() documentation will make you understand that the view has to be drawn at least once so the animation can be executed :
Returns true if this view has been through at least one layout since
it was last attached to or detached from a window.
However, wrapping the animation launch in a message worked for me. So you should try to do it like :
myView.post{
TransitionManager.beginDelayedTransition(topConstraintLayout);
constraintSet.applyTo(topConstraintLayout);
}
to create animations using ConstraintLayout and ConstraintSet you must first consider that there must be a starting layout and an ending layout
Step 1: Create your layouts
create your starting layout and ending layout as you want
--> suppose the starting layout name is (activity) the ending layout name is (activity_alt)
Step 2: Create Animation
now in the MainActivity inside onCreate method call your function foo
private void foo() {
var set = false
val constraint1 = ConstraintSet()
constraint1.clone(root)
val constraint2 = ConstraintSet()
constraint2.clone(this, R.layout.activity_alt)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
TransitionManager.beginDelayedTransition(root)
val constraint = if(set) constraint1 else constraint2
constraint.applyTo(root)
set = !set
}
}
Related
I'm having an issue with this for two days now... I have a binding and an image to pop when clicking a button.
I want that every time the button is pressed another image will pop either next to the pervious one, or, if there's no space in the linear layout, that it will pop ontop of the previous one.
The idea is to get a card from a card deck and put it ot the hand of the player.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPlayTableBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
var image = findViewById<ImageView>(R.id.fight_deck)
var i: Int = 1
val deck = Deck()
var deckListOfCards: MutableList<Card> = deck.shuffledDeckOfCard()
val deck_clickable_top = findViewById<ImageButton>(R.id.deck_button)
val imageView = ImageView(this)
val Width = convertDpToPixel(135f, this)
val Height = convertDpToPixel(190f, this)
imageView.layoutParams = LinearLayout.LayoutParams(Width.toInt(), Height.toInt())
var imgResId: Int = 0
deck_clickable_top.setOnClickListener {
if (deckListOfCards.size != 0) {
imgResId = deckListOfCards.get(0).getCardFace()
i += 1
deck.removeCardFromDeck(deckListOfCards.get(0))
}
if (deckListOfCards.size == 0) {
deck_clickable_top.visibility = View.INVISIBLE
deck_clickable_top.layoutParams.height = 0
}
imageView.setImageResource(imgResId)
}
binding.bottomLinearLayout.addView(imageView)
}
I can't figure out how to do it...
Well, LinearLayout doesn't seem like a good choice for this, but you can try something along these lines:
// step 1 - this is the linear layout you'll use as the container
val containerBinding = CustomLinearLayoutContainerBinding.inflate(inflater, container, false)
// step 2 - on some click event you can create a new binding, this should contain you image
val componentBinding = CustomComponentLayoutBinding.inflate(inflater, container, false).apply {
// set the image drawable here, text, any other data in here
}
// step 3 - the root layout of that binding is the linear layout
containerBinding.root.addView(componentBinding.root)
// the following 2 methods of LinearLayout can be very handy
removeAllViews() // to remove all previously added children
invalidate() // to re-render the linear layout
But please note that everything you want to do with those views after you added them will need manual changes and again, this doesn't feel like you should go for it. However, I found this link, check out this implementation, feels like this suits your requirements better.
Hi all I have a problem in displaying image in ImageView when clicked, i made a function to display whenever the image was click I have already added the android:onClick="drops" in .xml in every imageview of my sample game, i used gridlayout(3x3) with 9 imageview
here is the code of the function.
fun drops(view: View){
val imageView = ImageView(this)
var player = 0;
if (player == 0){
imageView.setImageResource(R.drawable.yellow)
imageView.animate().alpha(1f).rotation(360f).setDuration(600)
player == 1
}else {
imageView.setImageResource(R.drawable.red)
imageView.animate().alpha(1f).rotation(360f).setDuration(600)
player == 0
}
}
I'm unsure in what class is this function contained, I suppose it is either in a Fragment or Activity. As you said, you're binding the image views with drops method in the xml layout.
What could be the problem is this line:
val imageView = ImageView(this)
From your question we cannot see which class is this method defined in, so expression this could be anything. You might me instantiating an ImageView from the whole Activity or Fragment object, and cannot work in way you would like to. So I suggest to rewrite this line to something like this:
val imageView = view as ImageView
The function drops receives a single View parameter, which is the image view the user clicked - but View class has no method called setImageResource, so you need to cast it to the desired subtype (ImageView is subclass of View). This should do the job.
Background
It's possible to get the current locale direction, using this:
val isRtl=TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
It's also possible to get the layout direction of a view, if the developer has set it:
val layoutDirection = ViewCompat.getLayoutDirection(someView)
The problem
The default layoutDirection of a view isn't based on its locale. It's actually LAYOUT_DIRECTION_LTR .
When you change the locale of the device from LTR (Left-To-Right) locale (like English) to RTL (Right-To-Left) locale (like Arabic or Hebrew) , the views will get aligned accordingly, yet the values you get by default of the views will stay LTR...
This means that given a view, I don't see how it's possible to determine the correct direction it will go by.
What I've tried
I've made a simple POC. It has a LinearLayout with a TextView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/linearLayout" 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:gravity="center_vertical" tools:context=".MainActivity">
<TextView
android:id="#+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!"/>
</LinearLayout>
In code, I write the direction of the locale, and of the views:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
Log.d("AppLog", "locale direction:isRTL? $isRtl")
Log.d("AppLog", "linearLayout direction:${layoutDirectionValueToStr(ViewCompat.getLayoutDirection(linearLayout))}")
Log.d("AppLog", "textView direction:${layoutDirectionValueToStr(ViewCompat.getLayoutDirection(textView))}")
}
fun layoutDirectionValueToStr(layoutDirection: Int): String =
when (layoutDirection) {
ViewCompat.LAYOUT_DIRECTION_INHERIT -> "LAYOUT_DIRECTION_INHERIT"
ViewCompat.LAYOUT_DIRECTION_LOCALE -> "LAYOUT_DIRECTION_LOCALE"
ViewCompat.LAYOUT_DIRECTION_LTR -> "LAYOUT_DIRECTION_LTR"
ViewCompat.LAYOUT_DIRECTION_RTL -> "LAYOUT_DIRECTION_RTL"
else -> "unknown"
}
}
The result is that even when I switch to RTL locale (Hebrew - עברית), it prints this in logs:
locale direction:isRTL? true
linearLayout direction:LAYOUT_DIRECTION_LTR
textView direction:LAYOUT_DIRECTION_LTR
And of course, the textView is aligned to the correct side, according to the current locale:
If it would have worked as I would imagine (meaning LAYOUT_DIRECTION_LOCALE by deafult), this code would have checked if a view is in RTL or not:
fun isRTL(v: View): Boolean = when (ViewCompat.getLayoutDirection(v)) {
View.LAYOUT_DIRECTION_RTL -> true
View.LAYOUT_DIRECTION_INHERIT -> isRTL(v.parent as View)
View.LAYOUT_DIRECTION_LTR -> false
View.LAYOUT_DIRECTION_LOCALE -> TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
else -> false
}
But it can't, because LTR is the default one, and yet it doesn't even matter...
So this code is wrong.
The questions
How could it be that by default, the direction is LTR, yet in practice it gets aligned to the right, in case the locale has changed?
How can I check if a given View's direction would be LTR or RTL , no matter what the developer has set (or not set) for it ?
How could it be that by default, the direction is LTR, yet in practice it gets aligned to the right, in case the locale has changed?
The difference is in time. When the view is created it's assigned a default value until the real value is resolved. Actually there are two values maintained:
getLayoutDirection() returns the default LAYOUT_DIRECTION_LTR,
getRawLayoutDirection() (hidden API) returns LAYOUT_DIRECTION_INHERIT.
When raw layout direction is LAYOUT_DIRECTION_INHERIT the actual layout direction is resolved as part of the measure call. The view then traverses its parents
until it finds a view which has a concrete value set
or until it reaches missing view root (the window, or ViewRootImpl).
In the second case, when the view hierarchy is not attached to a window yet, layout direction is not resolved and getLayoutDirection() still returns the default value. This is what happens in your sample code.
When view hierarchy is attached to view root, it is assigned layout direction from the Configuration object. In other words reading resolved layout direction only makes sense after the view hierarchy has been attached to window.
How can I check if a given View's direction would be LTR or RTL , no matter what the developer has set (or not set) for it ?
First check, whether layout direction is resolved. If it is, you may work with the value.
if (ViewCompat.isLayoutDirectionResolved(view)) {
val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
// Use the resolved value.
} else {
// Use one of the other options.
}
Note that the method always returns false below Kitkat.
If layout direction is not resolved, you'll have to delay the check.
Option 1: Post it to the main thread message queue. We're assuming that by the time this runs, the view hierarchy has been attached to window.
view.post {
val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
// Use the resolved value.
}
Option 2: Get notified when the view hierarchy is ready to perform drawing. This is available on all API levels.
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
view.viewTreeObserver.removeOnPreDrawListener(this)
val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
// Use the resolved value.
return true
}
})
Note: You actually can subclass any View and override its onAttachedToWindow method, because layout direction is resolved as part of super.onAttachedToWindow() call. Other callbacks (in Activity or OnWindowAttachedListener) do not guarantee that behavior, so don't use them.
More answers to more questions
Where does it get the value of getLayoutDirection and getRawLayoutDirection ?
View.getRawLayoutDirection() (hidden API) returns what you set via View.setLayoutDirection(). By default it's LAYOUT_DIRECTION_INHERIT, which means "inherit layout direction from my parent".
View.getLayoutDirection() returns the resolved layout direction, that's either LOCATION_DIRECTION_LTR (also default, until actually resolved) or LOCATION_DIRECTION_RTL. This method does not return any other values. The return value only makes sense after a measurement happened while the view was part of a view hierarchy that's attached to a view root.
Why is LAYOUT_DIRECTION_LTR the default value ?
Historically Android didn't support right-to-left scripts at all (see here), left-to-right is the most sensible default value.
Would the root of the views return something of the locale?
All views inherit their parent's layout direction by default. So where does the topmost view get the layout direction before it's attached? Nowhere, it can't.
When a view hierarchy is attached to window something like this happens:
final Configuration config = context.getResources().getConfiguration();
final int layoutDirection = config.getLayoutDirection();
rootView.setLayoutDirection(layoutDirection);
Default configuration is set up with system locale and layout direction is taken from that locale. Root view is then set to use that layout direction. Now all its children with LAYOUT_DIRECTION_INHERIT can traverse and be resolved to this absolute value.
Would some modifications of my small function be able to work even without the need to wait for the view to be ready?
As explained in great detail above, sadly, no.
Edit: Your small function would look a little more like this:
#get:RequiresApi(17)
private val getRawLayoutDirectionMethod: Method by lazy(LazyThreadSafetyMode.NONE) {
// This method didn't exist until API 17. It's hidden API.
View::class.java.getDeclaredMethod("getRawLayoutDirection")
}
val View.rawLayoutDirection: Int
#TargetApi(17) get() = when {
Build.VERSION.SDK_INT >= 17 -> {
getRawLayoutDirectionMethod.invoke(this) as Int // Use hidden API.
}
Build.VERSION.SDK_INT >= 14 -> {
layoutDirection // Until API 17 this method was hidden and returned raw value.
}
else -> ViewCompat.LAYOUT_DIRECTION_LTR // Until API 14 only LTR was a thing.
}
#Suppress("DEPRECATION")
val Configuration.layoutDirectionCompat: Int
get() = if (Build.VERSION.SDK_INT >= 17) {
layoutDirection
} else {
TextUtilsCompat.getLayoutDirectionFromLocale(locale)
}
private fun View.resolveLayoutDirection(): Int {
val rawLayoutDirection = rawLayoutDirection
return when (rawLayoutDirection) {
ViewCompat.LAYOUT_DIRECTION_LTR,
ViewCompat.LAYOUT_DIRECTION_RTL -> {
// If it's set to absolute value, return the absolute value.
rawLayoutDirection
}
ViewCompat.LAYOUT_DIRECTION_LOCALE -> {
// This mimics the behavior of View class.
TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
}
ViewCompat.LAYOUT_DIRECTION_INHERIT -> {
// This mimics the behavior of View and ViewRootImpl classes.
// Traverse parent views until we find an absolute value or _LOCALE.
(parent as? View)?.resolveLayoutDirection() ?: run {
// If we're not attached return the value from Configuration object.
resources.configuration.layoutDirectionCompat
}
}
else -> throw IllegalStateException()
}
}
fun View.getRealLayoutDirection(): Int =
if (ViewCompat.isLayoutDirectionResolved(this)) {
layoutDirection
} else {
resolveLayoutDirection()
}
Now call View.getRealLayoutDirection() and get the value you were looking for.
Please note that this approach relies heavily on accessing hidden API which is present in AOSP but may not be present in vendor implementations. Test this thoroughly!
I've spent hours looking for answer and have really no idea how to solve it. So let's get down to business:
There is an image and a TextView and I need to flow the TextView around the ImageView like this:
First possible solution woult be to use https://github.com/deano2390/FlowTextView but it's not extending TextView so this library is not suitable for me for number of reasons.
Second solution would be to use LeadingMarginSpan.LeadingMarginSpan2 span but it affects on each paragraph for each n lines inside the text (like in this answer -> How to layout text to flow around an image), so I get smth like this:
But I wanted to set margin only for first n lines! Then I decided to implement LeadingMarginSpan.Standart and create a counter and increment it in getLeadingMargin(first: Boolean): Int function invocation. When the counter reach the desirable value, the function returns 0 as a margin width. And there is a fail again! Instead of filling the TextView lines, the text just moved left and didn't spread to the end of the view!
UPD: Yes, I've used onGlobalLayoutListener in here
Well, googling for another solution I found this answer https://stackoverflow.com/a/27064368/7218592
Ok, I've done everything as described and implemented the code:
//set left margin of desirable width
val params: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.leftMargin = holder.imageContainerHeight!!
params.addRule(RelativeLayout.BELOW, holder.mNumberAndTimeInfo!!.id)
holder.mCommentTextView!!.layoutParams = params
if (holder.commentTextViewOnGlobalLayoutListener != null)
holder.mCommentTextView!!.viewTreeObserver.removeOnGlobalLayoutListener(
holder.commentTextViewOnGlobalLayoutListener)
//add onGlobalLayoutListener
holder.mCommentTextView!!.viewTreeObserver.addOnGlobalLayoutListener(
if (holder.commentTextViewOnGlobalLayoutListener != null)
holder.commentTextViewOnGlobalLayoutListener
else CommentTextViewOnGlobalLayoutListener(holder,
SpannableString(HtmlCompat.fromHtml(
mView.getActivity(), commentDocument.html(), 0,
null, SpanTagHandlerCompat(mView.getActivity())))))`
My OnGlobalLayoutListener looks like this: `
class CommentTextViewOnGlobalLayoutListener(
val holder: CommentAndFilesListViewViewHolder, val commentSpannable: Spannable) :
ViewTreeObserver.OnGlobalLayoutListener {
val LOG_TAG: String = CommentTextViewOnGlobalLayoutListener::class.java.simpleName
override fun onGlobalLayout() {
holder.mCommentTextView!!.viewTreeObserver.removeGlobalOnLayoutListener(this)
//when textview layout is drawn, get the line end to spanify only the needed text
val charCount = holder.mCommentTextView!!.layout.getLineEnd(Math.min(
holder.mCommentTextView!!.layout.lineCount - 1,
CommentLeadingMarginSpan.computeLinesToBeSpanned(holder)))
if (charCount <= commentSpannable.length) {
commentSpannable.setSpan(CommentLeadingMarginSpan(holder),
0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
//set the left margin back to zero
(holder.mCommentTextView!!.layoutParams as RelativeLayout.LayoutParams).leftMargin = 0
holder.mCommentTextView!!.text = commentSpannable
}
}
`
Well, it works. But how terrible it works! As I'm using view holder pattern I have to hold a variable to the listener and remove if it is not been called and successfully removed because onGlobalLayout function wasn't called in time! And it is called too late, so you need to wait about 300 ms and then watch all the "reconstruction" of the TextView and it looks disgustingly!
So, my question is:
How to make margins for first n lines in TextView, before it's been drawn on UI?
This is more a suggestion that will only work with a little trial and error
This code uses a multi line Edit Text
btnPrint.setOnClickListener {
val str = """
One
Two
Three
Now click Action Button Custom SB
""".trimIndent()
etNews.setText(str)
}
Play with the One Two values indent and trimIndent has other properties available
I have a logo view, which is a full screen fragment containing single ImageView.
I have to perform some operations after the logo image is completely visible.
Following code is used to invoke the special task
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ImageView logoImageMaster = new ImageView(getContext());
//logoImageMaster.setImageResource(resID); //even after removing this, i am getting the callback twice
try {
// get input stream
InputStream ims = getActivity().getAssets().open("product_logo.png");
// load image as Drawable
Drawable d = Drawable.createFromStream(ims, null);
// set image to ImageView
logoImageMaster.setImageDrawable(d);
}
catch(IOException ex) {
}
logoImageMaster.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() { //FIXME get called twice. Check this out, no info to distinguish first from second
// Log.e("PANEL", "onGlobalLayout of Logo IV ---------------------------------");
activityInterface.doSpecialLogic();
}
});
return logoImageMaster;
}
My exact problem is, onGlobalLayout is called twice for this view hierarchy.
I know that onGlobalLayout is invoked in performTraversal of View.java hence this is expected.
For my use case of Single parent with Single child view, I want to distinguish the view attributes such that doSpecialLogic is called once[onGlobalLayout is called twice] , after the logo image is completely made visible.
Please suggest some ideas.
OnGlobalLayoutListener gets called every time the view layout or visibility changes. Maybe you reset the views in your doSpecialLogic call??
edit
as #Guille89 pointed out, the two set calls cause onGlobalLayout to be called two times
Anyhow, if you want to call OnGlobalLayoutListener just once and don't need it for anything else, how about removing it after doSpecialLogic() call??
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//noinspection deprecation
logoImageMaster.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
logoImageMaster.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
activityInterface.doSpecialLogic();
It seems to be called one time for each set done over the imageView
logoImageMaster.setImageResource(resID);
logoImageMaster.setImageDrawable(d);
You should Try using kotlin plugin in android
This layout listener is usually used to do something after a view is measured, so you typically would need to wait until width and height are greater than 0. And we probably want to do something with the view that called it,in your case
Imageview
So generified the function so that it can be used by any object that extends View and also be able to access to all its specific functions and properties from the function
[kotlin]
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
[/kotlin]
Note:
make sure that ImageView is described properly in the layout. That is its layout_width and layout_height must not be wrap_content. Moreover, other views must not result in this ImageView has 0 size.