I'm currently working on animation that will grow up the view if the user clicks it. Basically, its a card that, when clicked, it will reveal the bottom content. For that, I'm extending Animation like this:
Val collapseAnimation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val interpolatedInverted = 1 - interpolatedTime
val headerLp = headerImage.layoutParams
headerLp.width = ...
headerImage.layoutParams = headerLp
}
}
The problem is that i need to get the height of a view (wrap_content) that is defined in XML as 0dp. Basically, I want to grow up a view from 0dp to wrap_content and for that i need to know what is the wrap_content size.
How can I accomplish that in the most efficient way, without hard coding the view size?
In order to measure a view with different layout params and get its height, we can do the following:
contentContainer.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
val contentContainerFinalHeight = contentContainer.measuredHeight
Related
I want to add a list of views to a layout. The views are added, but are not showing up.
val container:LinearLayout=findViewById(R.id.container)
val card:FrameLayout=findViewById(R.id.card)
card.setBackgroundColor(Color.parseColor("#E67E22"))
val cardlimiter=4
var cards= arrayOfNulls<FrameLayout>(10)
for(i in 0 until cardlimiter)
{
cards[i]= FrameLayout(card.context).apply{
setBackgroundColor(Color.parseColor("red"))
}
container.addView(cards[i])
}
In Android we already have a component for this approach for us, It's called Recyclerview, check this doc: https://developer.android.com/develop/ui/views/layout/recyclerview
Nothing showing up because of FrameLayout you are trying to add doest not have any width, height or padding. You must specify a valid LayoutParam or nothing will shown with empty width or height.
Try to take a look at how to programmaticly create a valid layoutParam with width and height.
val params : ViewGroup.LayoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
FrameLayout(card.context).apply {
setBackgroundColor(Color.parseColor("red"))
layoutParams = params
setPadding(10, 10, 10, 10)
}
I am trying to create a layout where items would follow one another in columns (see image below) but I am not getting there yet. I have tried GridLayoutManager and StaggeredGridLayoutManager - the problem with both neither provides the feature of item flowing into another column and following each other this way. With my current attempt I am trying FlexboxLayoutManager but the result I am getting is always columns with single items instead of the items flowing one after another.
The desired behavior is that the items are located one after another and when the high of the recycler doesn't allow for the full item view it should be broken down to the next column.
Here is what I am trying right now:
mBinding?.activeRecycler?.layoutManager = FlexboxLayoutManager(context).apply {
flexDirection = FlexDirection.COLUMN
flexWrap = FlexWrap.WRAP
alignItems = AlignItems.STRETCH
}
And this is getting me one item per column.
Trying to achieve this:
I highly doubt this is possible.
The RecyclerView, its adapters and its layout managers all are not designed to alter the fundamental form of a view.
Meaning that "splitting" one would not be possible.
The RecyclerView is designed to understand how many views are in sight at the same time, create that many views only and then bind the underlying objects to the views respectively.
Meaning the RecyclerView doesn't "Cut a View in half and displays its halves in different places".
The only way in which a constellation like yours would be possible, was if the layout manager is specifically designed to display one item in multiple views and thereby multiple positions. Which would then allow it to be displayed as you described. However, as I said, that would mean the view 3 in the middle and the view 3 in the last column would be two views being bound to the same object or a copy of it. (Or someone went completely crazy and actually split the view, which I doubt).
I don't believe that any of the standard layout managers are capable of it and I doubt that you can even achieve this without also altering the adapter accordingly, at the very least. Because the adapter basically does the binding so without its help the standard layout managers wouldn't be able to do the double binding as described above.
That being said, this is just a very good guess, going by the principles of the view and its components. I have not read the source code or full description of every layout manager.
The way I understand your problem is like this: You have your current list of data that contains the text fields and you want to show them on the normal way, one list item one view item in recycler view.
But based on your design requirements this is not possible.
My idea to achieve that is like this:
You have to create a new list which will separate one item of the previous list into 2,3 or more items to fit in your columns.
private fun demo() {
val originalList = listOf<String>()
val newScreenSpecificList = mutableListOf<String>()
val columnHeight = 3//example number of lines
val columnWidth = 10//example number of chars
var columnsIndex = 0//index of column
var currentColumnHeight = 0 // current column filled height
originalList.forEach {
if (currentColumnHeight + getTextHeight(it, columnWidth) <= columnHeight) {
newScreenSpecificList.add(it)
currentColumnHeight = currentColumnHeight + getTextHeight(it, columnWidth)
} else {
//here is the part where your text is bigger then your column height so you need to divide it
val textForSpaceLeft = getTextForSpaceLeft(it, columnHeight - currentColumnHeight)
newScreenSpecificList.add(textForSpaceLeft)
currentColumnHeight = currentColumnHeight + getTextHeight(textForSpaceLeft, columnWidth)
if (currentColumnHeight >= columnHeight) {
columnsIndex++
}
if (getTextForNewSpaceLeft(it, columnHeight - currentColumnHeight)){
//continue to repeat logic for new column
//...
}
}
if (currentColumnHeight >= columnHeight) {
columnsIndex++
}
}
}
private fun getTextForSpaceLeft(it: String, spaceLeft: Int): String {
return "it"// return text for the available space
}
private fun getTextForNewSpaceLeft(it: String, spaceLeft: Int): String {
return "new column also"// return text left for the new available space
}
private fun getTextHeight(text: String, columnWidth: Int): Int {
return 2//todo your logic to convert text length to number of lines needed for a specific width of the column
}
Now you need to continue this logic it is not complete, I hope it helps you.
I guess your problem is with the LayoutParams of items which are being created in your adapter. probably the height is set to match_parent in items. You can try to change the LayoutParams of itemViews in your adapter's onCreateViewHolder/onBindViewHolder. Or if the items' heights are kinda tricky to calculate, you can create a customView and try calculate the height in onMeasure and set the height to wrap_content
try to set items' height to wrap_content or if you want to do it in code, something like this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlexItemViewHolder {
val infatedView = ...
infatedView.layoutParams = FlexboxLayoutManager.LayoutParams(FlexboxLayoutManager.LayoutParams.WRAP_CONTENT, FlexboxLayoutManager.LayoutParams.WRAP_CONTENT)
infatedView.addView(textView)
return FlexItemViewHolder(f)
}
I have the following code:
(setViewHolder is called in Adapter's OnCreateViewHolder)
override fun setViewHolder(parent: ViewGroup): BaseViewHolder<Task> {
return if (isHorizontal) {
val view =
LayoutInflater.from(parent.context)
.inflate(R.layout.horizontal_recycler_item, parent, false)
// Overriding default width
val params = view.layoutParams as ViewGroup.MarginLayoutParams
params.width = ((parent.width / 2.15) - params.marginStart - params.marginEnd).toInt()
view.layoutParams = params
TaskViewHolder(view)
} else {
val view =
LayoutInflater.from(parent.context)
.inflate(R.layout.details_list_item, parent, false)
TaskViewHolder(view)
}
}
And everything works fine until I refresh the fragment. And that's when the RecyclerView just becomes invisible. However, removing the line, where I set the width, from setViewHolder solves my issue.
What causes this?
The reason it becomes invisible is because you set width with parent.width directly,
We can not get the true value from parent.width when it is not measured.
Here provides two ways you can achieve for your requirement.
1. Calculate a View's dimension in layout after measure
view.measure(View.MeasureSpec, View.MeasureSpec)
After view.measure, we can call view.measuredWidth or view.measuredHeight to get the true value and calculate the size you want.
(view.measuredWidth / 3).toInt()
You just only need to find out what MeasureSpec you need to calculate.
But note that measure will caused a heavy-weight in loading, you need to use it carefully.
2. Set value by the screen size
We can get screen resolution in DisplayMetric by context.resources.displayMetric, so you can calculate the size you want
(context.resources.displayMetric.width / 3).toInt()
Each row in my RecyclerView is a single-row TextView with a certain size.
I'm looking for a general implementation such that the RecyclerView's height is set to correspond to the height of N such rows.
At the moment I'm using a magic value corresponding to 3 * rowHeight but it wouldn't be valid if I would change the text size of TextView.
Is this possible?
Edit: all rows have the same height
Make your itemView a LinearLayout which have vertical orientation. Then give it's width and height programmatically in your viewHolder like this.You have to calculate each rows width and height.
LayoutParams params = layout.getLayoutParams();
params.height = your_height;
params.width = your_width;
layout.setLayoutParams(params);
However if you have only 2,3 or 4 different heights, I mean these are specific for your recyclerView, you have to use getItemViewType() and make your different layouts and different ViewHolders for each type.
I ended up using the solution given in the comment.
Inflating the row view and then changing the layout params.
fun calculateRowHeight(layoutInflater : LayoutInflater, large: Boolean = true): Float {
val textView = layoutInflater
.inflate(R.layout.recycler_view_row, null) as TextView
val fm = textView.paint.fontMetrics
return fm.descent - fm.ascent
}
recyclerView.apply {
...
layoutParams.height = N * rowHeight.toInt()
}
TL;DR: Here's the gist of everything that I can think of that's relevant to the issue I'm facing: [GIST LINK]
And here's a picture of the problem
I'm trying to set up a number of buttons that will all grow to the same size as each other by equal weighting in a vertically oriented LinearLayout container.
The problem I'm facing surfaces when the text on these buttons cause a different number of lines per button.
Let's say n is the lowest line count for the buttons and m is the highest line count; any descenders in the text of buttons with line count m are cut off. Refer to the words "qshowing my clipping problem" in the linked screengrab, where all descenders are cut off.
How can I go about fixing this? The clipping gets much worse if I introduce android:lineSpacingExtra to the button style.
If it's relevant, my minimum API is set to 21
I've fixed this using RxJava to set the height programmatically to the correct maximum so that no clipping occurs. If there is a better solution I'll be glad to see it, but this is what is working for me for now:
class MyActivity {
// ...
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.my_activity)
// ...
val container: LinearLayout = findViewById(R.id.container)
val numBtns = getNumBtnsToAdd()
val btnList: MutableList<Button> = mutableListOf()
val margin10 = dpToPx(10f).toInt()
val countDown = CountDownLatch(numBtns)
val desiredLp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0).apply {
gravity = Gravity.CENTER
setMargins(margin10, margin10, margin10, margin10)
}
// Completable will be run once per subscriber and emit a success or error
val layoutCompletable = Completable.fromAction {
countDown.await()
for (btn in btnList) btn.layoutParams = desiredLp
}.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
compositeDisposable.add(
layoutCompletable.subscribe({
Log.d("MyActivity", "Set LayoutParams on all buttons.")
}, Throwable::printStackTrace)
)
for (i in 0 until numBtns) {
val btn = Button(this, null, 0, R.style.button_style).apply {
layoutParams = LinearLayout.LayoutParams(desiredLp).apply { height = LinearLayout.LayoutParams.WRAP_CONTENTS }
text = if (i == 0) "Button${i+1} with short text"
else "Button${i+1} with text that will span multiple lines showing my clipping problem"
setOnClickListener { doSomething() }
}
val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
countDown.countDown()
val height = btn.height
if (height > desiredLp.height) desiredLp.height = height
btn.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
btn.viewTreeObserver.addOnGlobalLayoutListener(listener)
btnList.add(btn)
container.addView(btn)
}
// ...
}
override fun finish() {
compositeDisposable.clear()
super.finish()
}
// ...
}
My guess, the main cause is buttons have fixed size. More preciously, you use LinearLayout to share available room between buttons via weight attribute. You can see the single line button height is same with 2-lines button height. So 2-lines buttons are forced to clip the text.
According your XML file you want enable the vertical scroll when there is no more room. In this case you don't need to use weight attribute. Just buttons under each other with margins.