Good day,
I need to show a list of fidelity cards, one on the others with a small border margin, and while clicking on it, it reveal it completely while sliding.
In a fragment, I use a ConstraintLayout somewhere in the screen that I populate by code.
I add all child's (total known after WS called) into the ConstraintLayout, then I am using ConstraintSet to set the initial constraints.
But at the initial fragment creation, I have found out that I must apply a TimeOut between the ConstraintLayout population and creating the Constraints (disgusting, right?)
adding each card...
val card = FrequencyCardView()
fid_card_container.addView(card as View)
listCard.add(card)
Then create the constraints:
val set = ConstraintSet()
val containerId = fid_card_container.id
set.clone(fid_card_container)
listCard.forEachIndexed { index, card ->
val marginTop = when (index) {
0 -> 0
else -> (listCard[index -1] as ViewGroup).height / 4
}
val rootId = if (index == 0) containerId else (index - 1)
set.connect(card.getId(), TOP, rootId, TOP, marginTop)
set.connect(card.getId(), ConstraintSet.START, containerId, ConstraintSet.START, 0)
set.connect(card.getId(), ConstraintSet.END, containerId, ConstraintSet.END, 0)
card.setOpen(false)
}
// Apply the changes
set.applyTo(fid_card_container)
Any idea, why I need to apply any timeout between adding the Childs into the ConstraintLayout and then add the Constraints?
Any better solution, that this ugly TimeOut?
Thanks
I have found a better way.
It seems that the constraints creation must be ensured to be done on the UI Thread. This is why it does work with the timeout.
So, I have changed the timeout call by the Anko call:
doAsync {
uiThread {
resetCardLayout()
}
}
Related
I'm having troubles with some animation in a recycler view. I do the relevant measurements in onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: PairingViewHolder) {
super.onViewAttachedToWindow(holder)
// get originalHeight & expandedHeight if not gotten before
if (holder.expandedHeight < 0) {
// Execute pending bindings, otherwise the measurement will be wrong.
holder.itemViewDataBinding.executePendingBindings()
holder.cardContainer.layoutParams.width = expandedWidth
holder.expandedHeight = 0 // so that this block is only called once
holder.cardContainer.doOnLayout { view ->
holder.originalHeight = view.height
holder.expandView.isVisible = true
// show expandView and record expandedHeight in next layout pass
// (doOnPreDraw) and hide it immediately.
view.doOnPreDraw {
holder.expandedHeight = view.height
holder.expandView.isVisible = false
holder.cardContainer.layoutParams.width = originalWidth
}
}
}
}
The problem is that doOnPreDraw gets called just for some views. It is something related to the visibility of the views I guess, since the smaller the items (expanded) are, the highest the count of the ones on which onPreDraw gets called.
My guess is that since I'm expanding them in onLayout, the recyclerView consider visible only the ones that when expanded are actually visible on screen. In onPreDraw I collapse them, resulting in some views being able to animate correctly and some not.
How would you solve this?
Thanks in advance.
I have a constraint
app:layout_constraintStart_toStartOf="parent". Runtime I need to change this constraint to app:layout_constraintStart_toEndOf="#+id\myid" .
From my research i found only constraintSet.connect(viewid, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 50); . But how to achieve my requirements with this. Any idea on this?
This is in kotlin but it's barely different from Java.
Give an ID to the rootLayout.
Now, use this code:
val constraintSet = ConstraintSet()
constraintSet.clone(rootLayout) //Your rootLayout
constraintSet.connect(
yourView.id, //ID of the view whose position you want to change
ConstraintSet.START,
yourMyIdView.id, //ID of the correspondent view
ConstraintSet.END
)
constraintSet.applyTo(rootLayout) //Your rootLayout
ProTip : You can also animate the change by setting animateChange to true in rootLayout (XML) or you can use a Transition animation which I always prefer.
val transition: Transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator() //Or Any other Interpolator
transition.duration = 700 //Duration
TransitionManager.beginDelayedTransition(rootLayout, transition) //Your rootLayout
constraintSet.applyTo(rootLayout) //Remember to put above 4 lines before this
You can further add Alpha animation it by using yourView.animate().alpha(1f).duration = 700 //1f (0 to 1) is the value and 700 is the duration.
Remember, it is barely different from Java, you just have to add ; at the end possibly.
I want to create a view (i.e. TextView) in my constraint layout and animate it's position on button click.
This is how my onClick looks like:
fun onClick(view: View) {
val layout = findViewById<ConstraintLayout>(R.id.layout)
val txt2 = TextView(this)
txt2.text = "Hello2"
txt2.textSize = 70F
txt2.id = View.generateViewId()
tid = txt2.id
layout.addView(txt2)
val cs = ConstraintSet()
cs.clone(layout)
cs.connect(txt2.id, ConstraintSet.BOTTOM, textbox.id, ConstraintSet.TOP, 0)
cs.connect(txt2.id, ConstraintSet.LEFT, layout.id, ConstraintSet.LEFT)
cs.connect(txt2.id, ConstraintSet.RIGHT, layout.id, ConstraintSet.RIGHT)
cs.applyTo(layout)
startAnimation(txt2.id)
}
fun startAnimation(txt: Int) {
val layout = findViewById<ConstraintLayout>(R.id.layout)
TransitionManager.beginDelayedTransition(layout)
val cs2 = ConstraintSet()
cs2.clone(layout)
cs2.connect(txt, ConstraintSet.TOP, layout.id, ConstraintSet.TOP, 50)
cs2.applyTo(layout)
}
Animation works if I create TextView somewhere before actual click, but how to do this in one shot?
I think problem in the way how android calculate layout. When you call TextView(this) and layout.addView(txt2) view created and added to children list, but have no height, width and position on screen. In Looper of UI thread will be enqueued task to layout textView, that call onMeausure and layout for txt2 and its children. If it enqueued in UI thread, it will be copmlite after your functions finish, and you start animation before tx2 was "layouted".
I see 2 ways to resolve this. First: create txt2 in layout on start of activity or specify it in xml, just not create constrains for it and set visibility to GONE. ConstraintSet.applyTo correctly works with appearing and disappearing when it change visibility. Second: put starting of animation as delayed. Not the perfect solution, but it should works.
Handler().postDelayed({
startAnimation(txt2.id)
}, 100)
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
}
}
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