Android : transfer a view from a parent to another one - java

I come here help since I don't understand at all my my code isn't working.
To be quick, my goal is to "reload" a View that represents a list item. Since my list item can contain other list items in it's children, I want to inflate a new list item, and then transfer those children from the old one to the new one.
I get a "The specified child already has a parent. You must call removeView() on the child's parent first." error, but I do call a removeView on the child's parent (somehow it doesn't work) (see my code after)
Here is how my layout is designed (I'm removing some lines so it is more readable) :
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="mainListItem"
type="com.plg.lirs.data.LirsDataEntity" />
</data>
<LinearLayout android:id="#+id/main_list_item_global_layout">
<LinearLayout android:id="#+id/main_list_item_parent_layout"
app:mainListItemParentLayout="#{mainListItem}">
<!-- contains a bunch of views and stuff, nothing important here -->
</LinearLayout>
<LinearLayout android:id="#+id/main_list_item_children_layout"
android:animateLayoutChanges="true">
<!-- here are all the children i want to transfer, all the children here are inflated from this layout -->
</LinearLayout>
</LinearLayout>
</layout>
Now here my code to inflate this layout :
/* entity is just a logical class that contains my data
olderView is the old view representing the old list item */
private fun inflateItem(entity: LirsDataEntity, olderView: View? = null) : View {
val itemBinding = DataBindingUtil.inflate<MainListItemBinding>(inflater, R.layout.main_list_item, null, false, null)
// the itemBinding.root will be added into the parent's children layout later on, after this function
// + i've tried with true as attachToParent, doesn't change
/* HERE is the core of the problem. My goal is : if an olderView is provided, then transfer the children from the old one to the new one */
if(olderView != null) {
val olderChildrenLayout = olderView.findViewById<LinearLayout>(R.id.main_list_item_children_layout) // here is the LinearLayout that contains the children
val children = olderChildrenLayout.children
children.forEach {
olderChildrenLayout.removeView(it) // remove the children from the old parent
itemBinding.mainListItemChildrenLayout.addView(it) // add it to the new parent
// at this point i get the error
}
}
entity.ui.reset() // not important here
itemBinding.mainListItem = entity
/* some listeners are set here */
return itemBinding.root
}
Thanks for reading !

I find out what were wrong.
When calling removeView(), android tries to animate it, thus placing the child view into a variable containing the children that are currently being animated. Then, when trying to change the child view's parent (which we want to be null), it checks it the current view is being animated. As it's true, the parent doesn't change (for now at least, I don't know if it will be changed later on). That's why we can't call the addView().
The solution is to store the LayoutTransition class, then setting it to null, do the transfer, and then resetting it. It will not animate the children, but at least it will work.
Here is a little piece of code to make that work:
public class JavaUtils {
public static void transferChildren(#NotNull final ViewGroup depart, #NotNull final ViewGroup arrival) {
LayoutTransition transition = depart.getLayoutTransition();
depart.setLayoutTransition(null);
while(depart.getChildCount() > 0) {
View c = depart.getChildAt(0);
depart.removeViewAt(0);
arrival.addView(c);
}
depart.setLayoutTransition(transition);
}
}
And for Kotlin users :
fun ViewGroup.transferChildrenTo(arrival: ViewGroup) {
val transition: LayoutTransition = layoutTransition
layoutTransition = null
while (childCount > 0) {
val c: View = getChildAt(0)
removeViewAt(0)
arrival.addView(c)
}
layoutTransition = transition
}

Related

When does expression get evaluated in Android Data-binding?

With Android data-binding framework, I understand that you can pass an object that extends baseObservable to the layout xml, use #Bindable on getters and do notifyPropertyChanged(BR.xxx) to have the related part re-evaluated.
What I don't understand is this: if you don't use most the stuff above and just call the getter directly in xml, when would it be evaluated?
Here's the code:
my_widget.xml
<layout 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">
<data>
<variable
name="someViewModel"
type="com.example.SomeViewModel" />
</data>
<androidx.cardview.widget.CardView>
<View
android:id="#+id/testView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="#{someViewModel.getName() ? View.VISIBLE : View.GONE}" />
</androidx.cardview.widget.CardView>
</layout>
MyView.java
MyWidgetBinding binding = MyWidgetBinding.inflate(LayoutInflater.from(mContext), parent, false);
binding.setSomeViewModel(someViewModel);
Questions:
If someViewModel.name ever changes, does the testView's visibility refreshes?
When does someViewModel.getName() get evaluated or how often?
If the expression is more complicated, something like:
android:visibility="#{func(otherVariable, someViewModel.getName()) ? View.VISIBLE : View.GONE}", say otherVariable is another variable defined in data section above, if somehow otherVariable gets re-set, then someViewModel.getName() will get evaluated and testView will reflect the latest visibility value, correct?
Following up on question 3, if otherVariable is changed to otherVariable.a where a is a 'bindable' field and notifyPropertyChanged(BR.a) is called in otherVariable then someViewModel.getName() will also get re-evaluated and testView will reflect the latest visibility value, correct?
Additionally, if I re-set someViewModel by calling binding.setSomeViewModel() but pass in the SAME someViewModel instance, does it do anything? Will the expression get re-evaluated?
I suggest you to create custom binding adapter for mutable visibility and use LiveData to update the visibility.
Code :
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Boolean>) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null) {
visibility.observe(
parentActivity,
Observer { value -> if (value) view.visibility = View.VISIBLE
else view.visibility = View.GONE})
}
}
To get the parent activity create ActivityExtensions.kt file and add the following function in it:
fun View.getParentActivity(): AppCompatActivity?{
var context = this.context
while (context is ContextWrapper) {
if (context is AppCompatActivity) {
return context
}
context = context.baseContext
}
return null
}
And in the ViewModel :
//Other code here...
val itemVisibility = MutableLiveData<Boolean>()
//Other logic here to init itemVisible
if(itemVisibile) itemVisibility.value = true else itemVisibility.value = false
And finaly the layoutItem :
<View
android:id="#+id/testView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mutableVisibility ="#{viewModel.itemVisibility}" />
If someViewModel.name ever changes, does the testView's visibility
refreshes?
it depends on the underlying technology you are using. LiveData? Yes BaseObservable you have to manually notify that the observed property changed.
When does someViewModel.getName() get evaluated or how often?
LiveData ? when you set/post a value. BaseObservable when you notify it
Same as point 2
If you change the underlying value and notify this change properly, it will get propagated accordingly. If you change the instance of the observed object it will not.

Android Recyclerview - How to have a different column count per row, based on child size?

I need to develop a tag picker, like the one Foursquare uses for tastes, and the one Flipboard uses for "finding new topics."
I came across this library, Foursquare-CollectionPicker at github.
However, it uses a linear layout, which can reduce the performance for numerous child views when scrolling.
Hence, I need to use a recyclerview. Can anyone suggest how to replicate this with a recyclerview? My problem is that for each row in the recyclerview, the column count could be different, based on the size/number of the child views in each row (tags, in this case).
Thank you.
You could use FlexboxLayoutManager in a recycler view. All you need to do is create the layout manager as below. Don't forget to add your own recyclerAdapter to the recycler view as well of course.
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(context);
layoutManager.setFlexWrap(FlexWrap.WRAP);
recyclerView.setLayoutManager(layoutManager);
https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
https://blog.devcenter.co/unboxing-the-flexboxlayout-a7cfd125f023
If all you need is a recycler view that changes the number of columns (using a standard Google provided GridLayoutManager in RecyclerView), you don't need any custom code at all.
(pseudo code)
Prerequisites
You use a RecylerView with a GridLayoutManager (import androidx.recyclerview.widget.GridLayoutManager)
Your Adapter has a Type (so different viewTypes can inflate different ViewHolders).
You can initialize your grid Layout like:
private lateinit var layoutManager: GridLayoutManager
private val adapter = YourAdapter()
Activity#onCreate(...) {
super.onCreate(savedInstance)
setContentView(...)
layoutManager = GridLayoutManager(this, 2) //defaults to two columns
yourRecyclerView.layoutmanager = layoutManager //set it
yourRecyclerView.adapter = adapter
//Here goes the magic
}
What is the Magic?
Something like this:
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter.getItemViewType(position)) {
adapter.viewTypeOneColumn -> 1
adapter.viewTypeTwoColumns -> 2
else -> -1
}
}
}
This obviously assumes you have a "view type" (of any sort)
it can be as simple as:
class YourAdapter : ... {
internal val viewTypeOneColumn = 0
internal val viewTypeTwoColumns = 1
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
viewTypeOneColumn -> ViewHolderForOneColumn(...)
viewTypeTwoColumns -> ViewHolderForTwoColumns(...)
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position).someThingThatClassifiesThem // this obviously depend on what you use to define what each item is...
}
}
And that's all you really need.
I have once created a sample that does this for displaying "ads" inside a RecyclerView: You can check it here (it was updated a while ago but the code still works the same).
https://github.com/Gryzor/GridToShowAds
You will need to write your own layout manager, take a look at these blogs:
http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/
http://simpleandstupid.com/2015/05/01/recyclerview-and-its-custom-layoutmanager/

How to get current content view in Android programming?

I know that I can set the content of the view in an Android app by saying setContentView(int). Is there a function I can use to know what the current content view is? I don't know if that makes any sense, but what I'm looking for is a function called, say, getContentView that returns an int.
Ideally, it would look like this:
setContentView(R.layout.main); // sets the content view to main.xml
int contentView = getContentView(); // does this function exist?
How would I do that?
Citing Any easy, generic way in Android to get the root View of a layout?
This answer and comments give one method: [Get root view from current activity
findViewById(android.R.id.content)
Given any view in your hierarchy you can also call:
view.getRootView()
to obtain the root view of that hierarchy.
The "decor view" can also be obtained via getWindow().getDecorView(). This is the root of the view hierarchy and the point where it attaches to the window, but I'm not sure you want to be messing with it directly.
You can do making a setter and getter of current view by id only
private int currentViewId = -1;
public void setCurrentViewById(int id)
{
setContentView(id);
currentViewId = id;
}
public int getCurrentViewById()
{
return currentViewId;
}
And then in
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setCurrentViewById(R.layout.main_layout);
}
Hope this helps.
In an Activity, you can do
View rootView = null;
View currentFocus = getWindow().getCurrentFocus();
if (currentFocus != null)
rootView = currentFocus.getRootView();
As described above, there is also
View decorView = getWindow().getDecorView();
as well as
View decorView = getWindow().peekDecorView();
The difference between the latter two is that peekDecorView() may return null if the decor view has not been created yet, whereas getDecorView() will create a new decor view if none exists (yet). The first example may also return null if no view currently has focus.
I haven't tried out whether the root view and the decor view are the same instance. Based on the documentation, though, I would assume they are, and it could be easily verified with a few lines of code.
if you have two content views then you can put a tag inside the relative layout of each one. and then get the view by tag name. if tag name is the one desire then blablabla. Hope this help for whoever is searching for a solution.

Use Custom View In XML Layout

I have a custom view:
public class Loading extends View {
private long movieStart;
private Movie movie;
public Loading(Context context, InputStream inputStream) {
super(context);
movie = Movie.decodeStream(inputStream);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
super.onDraw(canvas);
final long now = SystemClock.uptimeMillis();
if(movieStart == 0)
movieStart = now;
final int relTime = (int)((now - movieStart) % movie.duration());
movie.setTime(relTime);
movie.draw(canvas, 0, 0);
this.invalidate();
}
}
How can I use this view in XML layout? How can I pass the parameters (Context, InputStream) in XML layout?
How can I use this view in XML layout?
..
<pacakge_of_class.Loading
android:id="#+id/y_view1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
http://developer.android.com/guide/topics/ui/custom-components.html
There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
How can I pass the parameters
https://stackoverflow.com/a/4495745/804447
Error referencing an inner class View in layout/main.xml
<view class="Your_package.MainClass$Loading" />
The short answer is you can't directly do that.
The long answer is that you can indirectly do that.
Add the view to the XML by its fully qualified name (as others have mentioned), then:
What you need to do is implement the normal constructors from View. Define a custom attribute that declares the resource to use to create the InputStream in your constructor. The view system will give you the context automatically, you'd then need to open the InputStream based on the provided attribute value.
You can use a custom View in an XML-Layout like this:
<com.your.package.Loading
android:id="#+id/y_view1"
... />
But you cannot use your own constructor, you have to use the constructors as shown in this answer.
So you have to access your Loading View by code an set the InputStream manually:
Loading yourView = (Loading) findViewById(R.id.yourLoadingView);
yourView.setInputStream();
where you have this setter method in your Loading class:
public void setInputStream(InputStream inputStream){
movie = Movie.decodeStream(inputStream);
}

Can the parent view be notified when a child is created

In android UI can be created dynamically so is there a way to know if a child was created to a parent ?
For example, if i have a linear layout as a parent and i dynamically create a child button. Is there away to notify the parent ?
Tal Kanel's version will work, but to avoid repeating code, I'd suggest using a HierarchyChangeListener:
LinearLayout ll = (LinearLayout)findViewById(R.id.mylinearlayout);
ll.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
public void onChildViewAdded(View parent, View child) {
//handle the logic for an added child here
}
public void onChildViewRemoved(View parent, View child) {
//optionally, handle logic for a removed child
}
});
It's simple - if you have LinearLayout name linearLayout1, the only why to add child to him is
by calling the linearLayout.addView(View child) method.
so, you know exactly when the child added: it's can be only after this method called :)
example:
linearLayout1.addView(view);
doWhatYouWantToDoWhenChildAdded();

Categories