I'm trying to bind a divider decoration to a Recycler view
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:dividerDirection="#{}"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And then I have a BindingAdapter to bind the divider
#BindingAdapter("dividerDirection")
public static void setItemDecoration(RecyclerView view) {
LineDividerItemDecoration decoration =
new LineDividerItemDecoration(view.getContext());
view.addItemDecoration(decoration);
}
What is the correct way to bind a view without any extra arguments?
If I use something like
app:dividerDirection="#{#drawable/line_divider}"
It works fine
But it doesn't work with
app:dividerDirection="#{}"
I just need a reference to the view in order to get the context
You can't have a binding adapter without any parameter.
What you can do is to pass null.
app:dividerDirection="#{null}"
In this case, your binding adapter could look like this:
#BindingAdapter("dividerDirection")
public static void setItemDecoration(RecyclerView view, Integer direction) {
LineDividerItemDecoration decoration =
new LineDividerItemDecoration(view.getContext());
view.addItemDecoration(decoration);
}
I suggest creating binding adapters with general logic, not having it for every single use-case you have.
In your case, if you want to provide divider direction, you may pass integer/enum which would have some logic with the parameter.
recyclerView.addItemDecoration(new LineDividerItemDecoration(this).build());
Related
I've a vertical RecyclerView and each element of it, is a nested horizontal RecyclerView. Both have their Adapter and ViewHolder. When I change a flag, I want to be able to refresh the drawing of all items in each inner horizontal RecyclerView. I've written a method in the outer adapter that consequently call the inner one:
OuterAdapter:
public void setEditEnabled(boolean enabled) {
innerAdapter.setEditEnabled(enabled);
notifyDataSetChanged();
}
InnerAdapter:
public void setEditEnabled(boolean enabled) {
this.editable = enabled;
notifyDataSetChanged();
}
Then in the activity I call:
outerAdapter.setEditEnabled(editable);
outerRecyclerView.invalidate();
But only some "rows" are correctly updated...How can I solve this?
EDIT: so the flow is:
Outer setEditEnabled -> inner setEditEnabled -> inner notify -> outer notify
First of all, why are you calling invalidate on the RecyclerView. when you call notifyDataSetChanged() on the adapter, it automatically updates the RecyclerView items. And why is there only one inner Adapter. I think there should be a different adapter for each horizontal RecyclerView.
I don't know if I'm doing it right, but it seems to work fine.
Previously I was instantiating a new innerAdapter in the ViewHolder constructor:
class ViewHolder extends RecyclerView.ViewHolder {
RecyclerView innerRecyclerView;
ViewHolder(View view) {
super(view);
[...] // init things
innerRecyclerView.setAdapter(new InnerAdapter(context, onItemClickListener));
}
}
Now I'm doing the new in the OuterAdapter constructor and saving the reference as private field of the class:
public DailyMenusAdapter(Context context, OnItemClickListener onItemClickListener) {
innerAdapter = new InnerAdapter(context, onItemClickListener);
}
Then I pass the reference to:
innerRecyclerView.setAdapter(innerAdapter);
Is that ok in your opinion? So reusing the same adapter over and over again.
You know, I think if you will create annonimous Adapter class instead of notifyDataSetChanged (somethink like mRecyclerView.setAdapter(new YourAdapter(this, listOfDataYouPass))) it will work fine.
EDIT:Usually we do something like this to set adapter to RecyclerView:
List mLogs = new ArrayList<>;
mLogs.addAll (logs);
mLogAdapter = new LogAdapter(this, mLogs);
mRecyclerView = findViewById(R.id.log_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mLogAdapter);
and then, when we need to modify something we call mLogAdapter.notifyDataSetChanged. Or just call notifyDataSetChanged inside Adapter class. But in many cases it works wrong, hard to say why in your case.
So here is what I advise:
instead of call notifyDataSetChanged make this method and call it when you need to update or change something:
private void updateRecyclerView (){
mRecyclerView.setAdapter(new LogAdapter(this, mLogs));
}
Note: Do not pay attention to the names, I took code from the first project I got
I currently have a FirebaseListAdapter that populates a ListView with the last 5 items:
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
FirebaseListAdapter<HashMap> adapter = new FirebaseListAdapter<HashMap>(getParentFragment().getActivity(),HashMap.class,R.layout.list_item,firebase.limitToLast(5)) {
#Override
protected void populateView(View view, HashMap hashMap, int i) {
String title = hashMap.get("title").toString();
String body = hashMap.get("body").toString();
TextView title_txt = (TextView)view.findViewById(R.id.title);
TextView body_txt = (TextView)view.findViewById(R.id.body);
title_txt.setText(title);
body_txt.setText(body);
}
};
listView.setAdapter(adapter);
}
The problem I have is that when a new item is pushed to Firebase, it is added to the bottom of the list. I want the newest items at the top of the list.
Is it possible to achieve this?
Any help is greatly appreciated, thanks!
This is not the exact solution of your problem of getting the data from firebase in a reverse order, but anyway, we've other work arounds.
The first work around is mentioned in a comment of using a custom adapter for your list.
To achieve the behaviour you need to get the firebase data in a list and then you've to reverse it yourself before passing it to an adapter. Simple!
The second work around is easy as pie and I think if you're using RecyclerView it could do the trick for you and I see it as the simplest way you can do the job.
Here's I'm modifying some of my code with RecyclerView
// Declare the RecyclerView and the LinearLayoutManager first
private RecyclerView listView;
private LinearLayoutManager mLayoutManager;
...
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
// Use FirebaseRecyclerAdapter here
// Here you modify your LinearLayoutManager
mLayoutManager = new LinearLayoutManager(MainActivity.this);
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
// Now set the layout manager and the adapter to the RecyclerView
listView.setLayoutManager(mLayoutManager);
listView.setAdapter(adapter);
}
By setting mLayoutManager.setReverseLayout(true); - you're reversing your layout and mLayoutManager.setStackFromEnd(true); positions the view to the top of your list.
Migrating to RecyclerView is simple. Your layout will be something like this
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<android.support.v7.widget.RecyclerView
android:id="#+id/my_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
And in your build.gradle
dependencies {
compile 'com.android.support:recyclerview-v7:23.4.0'
}
You need to have FirebaseRecyclerAdapter can be found here in FirebaseUI library.
Note: Do not use RecyclerView.LayoutManager as the setReverseLayout and setStackFromEnd functions won't be found in RecyclerView.LayoutManager. Use LinearLayoutManager as stated.
Update
Here's how you can handle the click events of your items in the list.
You had to declare a ViewHolder to implement the RecyclerView right? Just add another function inside your ViewHolder class like the example below and call this function after the setText functions you've got there.
public static class MyViewHolder extends RecyclerView.ViewHolder {
View mView;
public MyViewHolder(View itemView) {
super(itemView);
mView = itemView;
}
public void setClickEvent() {
// Set the onClickListener on mView
// mView.setOnClickListener(new OnClickListener)...
}
}
Since you're already extending FirebaseListAdapter to implement populateView, you can go ahead and override getItem as well to invert the item lookup:
#Override
public HashMap getItem(int pos) {
return super.getItem(getCount() - 1 - pos);
}
If you're not using RecyclerView this is a simple way to reverse the data without extending any more classes than necessary. Obviously this will reverse any AdapterView subclass backed by this adapter, if you choose to reuse it.
I am building a notifications list for an application I'm working on and I'm having trouble finding a way to take my list of notifications from the server and displaying them in separate lists in a RecyclerView. The end product would display the list of notifications with headers for Recent notifications and Older notifications, a la:
<RECENT HEADER>
<NOTIF-1>
<NOTIF-2>
<OLDER HEADER>
<NOTIF-3>
<NOTIF-4>
<NOTIF-5>
<NOTIF-6>
except instead of angle-bracket text it's actual views representing those, complete with images, actual notification details and dividers.
I already have code that displays them in a RecyclerView:
XML:
<!-- Main layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/include_toolbar"/>
<RelativeLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/notification_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapjungle.mymoose.ui.widget.EmptyRecyclerView
android:id="#+id/notification_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
</LinearLayout>
Java:
#InjectView(R.id.notification_list) RecyclerView mRecyclerView;
#Inject Picasso mPicasso;
#Inject NotificationService mUserService;
private NotificationAdapter mAdatper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notifications);
ButterKnife.inject(this);
setTitle("Notifications");
mAdatper = new NotificationAdapter(mPicasso);
mRecyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(this)
.color(getResources().getColor(R.color.secondary_color))
.size(1)
.build());
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdatper);
updateList();
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_PHOTO_POST;
}
public void updateList() {
mUserService.getNotifications(new Callback<List<Notification>>() {
#Override
public void success(List<Notification> notificationList, Response response) {
mAdatper.replaceWith(notificationList);
}
#Override
public void failure(RetrofitError error) {
Timber.e(error, "Failed to load notifications...");
}
});
}
This all works fine enough to display all of the notifications and they're all sorted in the order from newest to oldest descending. But each has a boolean property "acknowledged" that is set to false if the user hasn't seen them before. I want to put split the list into the two groups I've explained above using this flag, but I don't know how to throw in the headers. I've thought about subclassing Notification to create NotificationHeader views and inserting them into the list where appropriate but that just feels sloppy to me. I've also thought about doing two recycler views, one for the new and another for the old, but visually that didn't work the way I intended (I haven't confirmed it but it looked like each recycler view scrolled independently of the others, something that I do not want). Any suggestions?
I know that the first idea of creating special Notification Headers will probably work, I've done something like that before, but it just feels like bad practice.
RecyclerView.Adapter has a method called getItemViewType() that takes the position of an item in the adapter's list, and returns the view type it should use. In my case, the method looks like this:
#Override
public int getItemViewType(int position){
Notification n = mNotifications.get(position);
boolean useHeader = n.getType().equals(Notification.HEADER_OLDER) ||
n.getType().equals(Notification.HEADER_RECENT);
return useHeader ? this.USE_HEADER : this.DONT_USE_HEADER;
}
Which checks the items in the notification list and sees if they're a special static 'Header notification' object. This is used internally by the Adapter class and it passes the 'viewType' parameter to the onCreateViewHolder() method, which we also override:
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
int layout = viewType == USE_HEADER ?
R.layout.view_item_notification_header :
R.layout.view_item_notification;
NotificationItemView view = (NotificationItemView) LayoutInflater.from(viewGroup.getContext())
.inflate(layout, viewGroup, false);
return new ViewHolder(view);
}
Overriding this method allows us to use the viewType parameter to choose the appropriate layout to inflate for the ViewHolder.
There are some better style/good practice decisions things I should have done here, such as making my Notification adapter hold a list of NotificationListItems instead of Notifications, which would allow me to put in a new kind of NotificationHeader object on it's own instead of making Notification objects that weren't really Notifications and using a bunch of constant values. But the underlying principle is still there:
In your Model, have a method that returns the layout view to use for it
In your adapter override getItemViewType() to use the aforementioned method and return an int that corresponds to the layout that should be inflated
In your adapter also override onCreateViewHolder() to use the int from getItemViewType() and inflate the appropriate view accordingly
I am new to android development so may be i am missing something elementary here, but after almost 2 days of googling I am still unable to figure out the cause.
I have followed the following article to create my project, except that I am invoking this activity from another activity when a button is clicked.
I have also created my own List adopter public class MyListAdapter extends ArrayAdapter<MyData> and in the getView() method of this adapter I am actually providing a different representation like below.
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.my_list_view, parent, false);
}
this all works fine I am able to bind the data and its displaying the results on the list without any issues. Now I want to show the details of selected MyData when the user clicks on the list item, but this is just not working no matter what I try.
I have ensured that my activity is implementing the callback interface like below:
public class MyItemListActivity extends FragmentActivity
implements MyItemListFragment.Callbacks
and the callback itself is also very simple:
public interface Callbacks {
public void onItemSelected(MyData item);
}
also on my MyItemListFragment the onAttched is correctly hooked up mCallbacks = (Callbacks) activity;
What I really notice is that the following function is just not called when I click on the list item:
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
MyData data = DataContent.ITEMS.get(position);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(data);
}
my fragment that displays the list item looks like this:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/myitem_list"
android:name="path.MyItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:context="path.MyItemListActivity"
tools:layout="#android:layout/list_content" />
and the actual custom adapter view my_list_view looks like below:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >....</RelativeLayout>
I can see that its missing the android:onClick tag here, but not sure if that is the cause and also who needs to implement this handler as there is no direct context on this layout.
Any help is really appreciated.
Regards
Kiran
I would like to improve the way i created the following UI. Currently i am creating each tablerow programmatically according to each object's type attribute.
class objectDTO {
private type; //enum
public boolean isMultiple(){...}
public boolean isSingle(){...}
}
I am trying to find a more dynamic solution like having a class for each type that might not requires programmatically adding layouts and components as i do in the fragment class,
if(objectDTO.isMultiple()) {
//Create TableRow + Multiple radiobuttons
}
else if(objectDTO.isSingle() {
//Create TableRow + Add One Checkbox
{
else {
//Create default invalid object Interface or skip
}
Havind a listadapter and applying the different ui there will just move the design problem to other class.
Early thanks for your contribution
Well, the simple solution for you would be to have a class hierarchy- a base objectDTO class and a child for each type. When you load the object list, have a factory method create the proper type of object. Each type would override a createView method which would create the view for that type. Then your table creation function becomes:
for(objectDTO object : allObjects){
View view = object.createView();
tableView.addView(view, lp);
}
But if you're creating a view for an object type, there's always going to need to be someone that dynamically creates view objects (createView in this case), and there's always going to need to be some function that knows what class to make an object (the factory in this case). Its just a matter of where you want that complexity to be.