Custom CollapsingTopAppBar Jetpack Compose - kotlin

The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.

you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go

Related

How to change the fonts of all items on recyclerview runtime

I wanted to change font family of items on a recycler view every time I click a button.
So I coded like below.
rbAritaBuri = view.findViewById(R.id.rb_aritaBuri)
rbCafe24 = view.findViewById(R.id.rb_cafe24SurroundAir)
rbAritaBuri.setOnClickListener {
rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/arita_buri.otf")
}
rbCafe24.setOnClickListener {
rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/cafe24_surround_air.ttf")
}
But it changes only the font family of the first item of the recycler view.
Is there a way to change fonts of them all together runtime? And please tell me why the code I wrote doesn't work right.
Thank you.
If I were in your position, I would:
Put your font changing calls inside of onBindViewHolder(). If you have to, you could put a bool in there like buttonClicked and link its value to your buttons.
Come up with a good way to force a call to onBindViewHolder(). Sometimes notifyDataSetChanged() is enough. But in some cases, you might have to remove the adapter by setting it to null and then reset the adapter to its original value.
Place that logic from step 2 inside of your buttons' onClick()s.
Edit:
What I mean is, create a var inside the class with the most exterior scope, so outside of oncreate().
var textChoice=""
Now use your buttons to change that var.
rbAritaBuri.setOnClickListener {
textChoice="fonts/arita_buri.otf"
}
Now inside your onBindViewHolder(), make the font switch.
when (fontChoice){
"fonts/arita_buri.otf"->{ rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/arita_buri.otf")}
//and so on and so forth for all of your fonts
Now when you want to show the change, call notifyDatasetChanged(). I think maybe the best place to do that would be inside of your buttons. So maybe you'd actually have:
rbAritaBuri.setOnClickListener {
textChoice="fonts/arita_buri.otf"
<The name of your recyclerview adapter>.notifyDatasetChanged()
}
Here is how I solved it, thanks to D. Kupra:
class SampleWorkAdapter(private val context: Context) :
RecyclerView.Adapter<SampleWorkAdapter.ViewHolder>() {
var selectedFont = EditTextActivity.HAMBAK_SNOW
First, I assigned the default font Hambak_snow to selectedFont, type String.
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
fun changeFont(font: String) {
CustomFontHelper.setCustomFont(content, font, itemView.context)
} ...
}
Then I wrote a function to be called on onBindViewHolder to change font-family of textview, using custom typeface. https://stackoverflow.com/a/16648457/15096801 This post helped a lot.
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
...
viewHolder.changeFont(selectedFont)
...
}
Now, replaceFont will be called when the variable selectedFont get changed and adapter.notifyDatasetChanged() is called on an activity, like this:
rbMapoFlowerIsland.setOnClickListener {
sampleWorkAdapter.selectedFont = EditTextActivity.MAPO_FLOWER
sampleWorkAdapter.notifyDataSetChanged()
}

RecyclerView custom LayoutManager remove unwanted views

I have a custom LayoutManager (inherited from LinearLayoutManager) that needs to calculate the item width of each child and remove all children from RecyclerView that has no space for them to appear.
Sample code (edited V2):
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
super.onLayoutChildren(recycler, state)
// skip if orientation is vertical, for now we only horizontal custom menu
if (orientation == RecyclerView.VERTICAL) return
// skip if adapter has no items
if (itemCount == 0) return
var totalItemWidth = 0
var totalItemsCanFit = 0
// calculate menu item width and figure out how many items can fit in the screen
for (i in 0 until childCount) {
getChildAt(i)?.let { childView ->
totalItemWidth += getDecoratedMeasuredWidth(childView)
}
if (screenWidth > totalItemWidth) {
totalItemsCanFit++
}
}
// if all items can fit, do nothing and show the whole menu
if (childCount > totalItemsCanFit) {
// remove child views that have no space on screen
for (i in childCount - 1 downTo totalItemsCanFit) {
removeAndRecycleViewAt(i, recycler)
}
}
}
I have 2 questions:
Is the sample code above the correct way to approach this problem?
How can I add a 3-dot icon at the end after seeing that not all items could fit?
EDIT:
To clarify, what I am trying to achieve is a popup menu backed by a RecyclerView. The menu has no item limit, instead it should calculate each item width and remove all items that have no space. Also, add a 3-dot menu item as a more option at the end.
Regarding your first question:
See if addDisappearingView(View child) could help you,
according to the documentation:
To be called only during onLayoutChildren(Recycler, State) to add a
view to the layout that is known to be going away, either because it
has been removed or because it is actually not in the visible portion
of the container but is being laid out in order to inform RecyclerView
in how to animate the item out of view.
As for the second question - you simply need to implement a 'load more' feature to your recyclerView. How you'll implement this is up to your needs/design (if you want a button or auto-scroll...).
There are many tutorials for that, for example: https://androidride.com/android-recyclerview-load-more-on-scroll-example/ .

Hide a view in Titanium so that it does not take physical space

In titanium it is possible to hide a view like so:
$.foo.hide()
or
$.foo.visible = false
However, in both cases the object still seems to take physical space. It is just invisible. In other words it is similar to the CSS property visibility: hidden.
I want it so that it disappears and take no physical space in terms of width or height, so it's similar to the CSS property display: none
How can I do this?
The best hacky solution I have is the following:
$.foo.width = 0;
$.foo.height = 0;
$.foo.left = 0;
$.foo.right = 0;
But that means when I want to make it visible again, I have to set all those properties back to their original values which is a pain and hard to maintain.
First of all, don't afraid of doing some hard coding ;)
Coming to your query, yes, this is true that hiding a view just hide it from UI, but physical-space is still there.
To do what you want, you will need to either remove view on hide & create it on show, or you can use absolute layout in some tricky way.
Other way could be to animate this view using transform property like this:
// on hide
$.foo.animate({
duration : 100,
transform : Ti.UI.create2DMatrix({scale:0})
}, function () {
$.foo.visible = false;
});
// on show
$.foo.visible = true; // we need to make it visible again before resetting its UI state since we hid it after completion of animation in above code
$.foo.animate({
duration : 100,
transform : Ti.UI.create2DMatrix() // passing empty matrix will reset the initial state of this view
});
OR
this could also work but never tried this:
// on hide
$.foo.transform = Ti.UI.create2DMatrix({scale:0});
$.foo.visible = false;
// on show
$.foo.visible = true;
$.foo.transform = Ti.UI.create2DMatrix();

Creating a flexible UI contianer for an image and a label

I thought this would be like pretty simple task to do, but now I have tried for hours and cant figure out how to get around this.
I have a list of friends which should be displayed in a scrollable list. Each friend have a profile image and a name associated to him, so each item in the list should display the image and the name.
The problem is that I cant figure out how to make a flexible container that contains both the image and the name label. I want to be able to change the width and height dynamically so that the image and the text will scale and move accordingly.
I am using Unity 5 and Unity UI.
I want to achieve the following for the container:
The width and height of the container should be flexible
The image is a child of the container and should be left aligned, the height should fill the container height and should keep its aspect ratio.
The name label is a child of the contianer and should be left aligned to the image with 15 px left padding. The width of the text should fill the rest of the space in the container.
Hope this is illustrated well in the following attached image:
I asked the same question here on Unity Answers, but no answers so far. Is it really possible that such a simple task is not doable in Unity UI without using code?
Thanks a lot for your time!
Looks like can be achieved with layout components.
The image is a child of the container and should be left aligned, the height should fill the container height and should keep its aspect ratio.
For this try to add Aspect Ratio Fitter Component with Aspect mode - Width Controls Height
The name label is a child of the container and should be left aligned to the image with 15 px left padding. The width of the text should fill the rest of the space in the container.
For this you can simply anchor and stretch your label to the container size and use BestFit option on the Text component
We never found a way to do this without code. I am very unsatisfied that such a simple task cannot be done in the current UI system.
We did create the following layout script that does the trick (tanks to Angry Ant for helping us out). The script is attached to the text label:
using UnityEngine;
using UnityEngine.EventSystems;
[RequireComponent (typeof (RectTransform))]
public class IndentByHeightFitter : UIBehaviour, UnityEngine.UI.ILayoutSelfController
{
public enum Edge
{
Left,
Right
}
[SerializeField] Edge m_Edge = Edge.Left;
[SerializeField] float border;
public virtual void SetLayoutHorizontal ()
{
UpdateRect ();
}
public virtual void SetLayoutVertical() {}
#if UNITY_EDITOR
protected override void OnValidate ()
{
UpdateRect ();
}
#endif
protected override void OnRectTransformDimensionsChange ()
{
UpdateRect ();
}
Vector2 GetParentSize ()
{
RectTransform parent = transform.parent as RectTransform;
return parent == null ? Vector2.zero : parent.rect.size;
}
RectTransform.Edge IndentEdgeToRectEdge (Edge edge)
{
return edge == Edge.Left ? RectTransform.Edge.Left : RectTransform.Edge.Right;
}
void UpdateRect ()
{
RectTransform rect = (RectTransform)transform;
Vector2 parentSize = GetParentSize ();
rect.SetInsetAndSizeFromParentEdge (IndentEdgeToRectEdge (m_Edge), parentSize.y + border, parentSize.x - parentSize.y);
}
}

Nested Grid in GWT

I need to develop a control which is similar to the Nested Grid in the Smart GWT.
User will be having a column for expansion images, when user clicking on the image in a particular row, a sub grid has to be opened there itself. Here all remaining rows needs to move down.
How can i achieve that functionality? Can anybody give me some clues so that i can proceed.
I have already a grid which is a celltable with custom header(with search functionality implemented).
Thanks,
Saritha.
Create your nested widget (myNestedWidget) that you want to show. It should have a CSS style "position: absolute", unless your grid is added to the LayoutPanel (or similar), in which case you can position your widget directly. Let's call the parent widget of your grid gridParentWidget.
In your CellTable add the following handler:
myTable.addCellPreviewHandler(new Handler<myObject>() {
#Override
public void onCellPreview(CellPreviewEvent<myObject> event) {
if ("click".equals(event.getNativeEvent().getType())) {
if (event.getColumn() == 0) {
int top = myTable.getRowElement(event.getIndex()).getAbsoluteBottom();
int left = myTable.getRowElement(event.getIndex()).getAbsoluteLeft();
myNestedWidget.getElement().getStyle().setTop(top, Unit.PX);
myNestedWidget.getElement().getStyle().setLeft(left, Unit.PX);
gridParentWidget.add(myNestedWidget);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
int height = myNestedWidget.getOffsetHeight();
myTable.getRowElement(event.getIndex()).getStyle().setHeight(height + "px");
]
});
}
}
});
}
This is obviously an outline of the solution. The details of the implementation may vary slightly depending on which widgets you use for your parent widget and your nested widget. If you change z-indexes somewhere, you have to take it into account too. You also need to make sure that your nested widget fits into the width of your grid, or you'll need to wrap it in a ScrollPanel and set a width to it explicitly.