I have a screen with a EditText to find content and a RecyclerView.
The EditText is to find in my web server
The RecyclerView shows most popular tags used
So, What I want to do is, when the user click any of the elements of the RecyclerView List
that value selected is passed to editText.
I have a fragment which inflate the file which show all the stuff:
tags_list.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org............BuscarFragment">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/inputSearch"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawableLeft="#android:drawable/ic_menu_search"
android:hint="#string/busqueda"
android:inputType="textVisiblePassword"
android:lines="1"
android:singleLine="true" />
<TextView
android:id="#+id/buscar_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/inputSearch" />
<TextView
android:id="#+id/etiquetas_populares"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/buscar_msg"
android:layout_marginLeft="16dp"
android:text="#string/etiquetas_populares"/>
<android.support.v7.widget.RecyclerView 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:id="#+id/tags_list"
android:name="org............TagsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/etiquetas_populares"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="org...........activities.TagsActivity"
tools:listitem="#layout/tags_list_content" />
</RelativeLayout>
</FrameLayout>
In TagsFragment.java I have this method:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.tags_list, container, false);
//RecyclerView Section
mRecyclerView = (RecyclerView) view.findViewById(R.id.tags_list);
mBuscarTagsAdapter = new BuscarTagsAdapter(getActivity(), tagMoreUsedRecyclerViewList);
mRecyclerView.setAdapter(mBuscarTagsAdapter);
addKeyListener();
return view;
}
Finally in my Adapter.
BuscarTagsAdapter.java
#Override
public void onBindViewHolder(final BuscarTagsViewHolder holder, int position) {
final int posicion = position;
//onClick for the list
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//When the user click I want to pass this value to my EditText, but
//I don't see how to do it from this Adapter
Toast.makeText(context, tags.get(posicion).getTagName(), Toast.LENGTH_SHORT).show();
}
});
}
Any idea?
Thanks
UPDATED and SOLVED:
Hi, #Brian Nam, thanks for help.
Made some changes.
First in Constructor as you suggested:
public BuscarTagsAdapter(Context context, List<TagMoreUsedRecyclerView> items, TagsListInterface tagsListInterface) {
this.context = context;
this.tags = items;
this.tagsListInterface = tagsListInterface;
}
but tagsListInterface is not initialized properly in TagsFragment,
so I added this in onCreate method:
tagsListInterface = new TagsListInterface() {
#Override
public void onTagClicked(String tagName, int posicion) {
EditText editText = (EditText) view.findViewById(R.id.inputSearch);
editText.setText(tagName);
}
};
Finally in onClick for the RecyclerView:
BuscarTagsAdapter.java
#Override
public void onBindViewHolder(final BuscarTagsViewHolder holder, int position) {
final int posicion = position;
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, tags.get(posicion).getTagName(), Toast.LENGTH_SHORT).show();
tagsListInterface.onTagClicked(tags.get(posicion).getTagName(), posicion);
}
});
}
Unfortunately, there's no "good" way to do this. I'd say instead of interfaces you should check out one of the event bus implementations like EventBus from Greenrobot.
https://github.com/greenrobot/EventBus
The good thing about using something like EventBus (basically the publisher-subscriber pattern) is that you spare a lot of anonym classes, interfaces but you also make the code less coupled. Not to mention any class can subscribe to the events so you can basically say "The user did this" and multiple fragments can react to it. This is especially useful if you do complex UI/animation stuff.
The bad thing about EventBus is that you start having a lot of events code quality suffers, and its sometimes hard to know what happens and where.
But as I said there's no "good" way to do this, thats just how the android framework works.
As #Brian Nam wrote, you can pass a reference to a TagsFragment instance to your BuscarTagsAdapter.
A similar but IMO a little bit more 'clean' solution would be to create an interface e.g.:
public interface TagsListInterface {
void onTagClicked(String tagName);
}
Then your TagsFragment would implement the interface and pass an instance of it to BuscarTagsAdapter. Then you would call the onTagClicked method of that interface when an element of the Recycler View is clicked.
Another solution is to communicate the RecyclerView with your Fragment via your Activity which, as I believe, is the most 'Android-like' one. It could also involve creating a custom interface but it is the Activity that would implement it instead of a Fragment. You can read more about it here. The article explains how to perform a communication between fragments but I believe it can show you the general idea so you can apply it to your situation.
Update:
In onCreateView method you are calling:
mBuscarTagsAdapter = new BuscarTagsAdapter(getActivity(), tagMoreUsedRecyclerViewList);
The only thing you have to change here is to update the constructor of BuscarTagsAdapter to receive an instance of TagsListInterface and then pass your implementation from TagsFragment. Then you are able to use it to call onTagClicked when an element is clicked.
Related
I'm having an issue with placing a RecyclerView inside a NestedScrollView, which causes ALL elements of the RecyclerView's adapter to be rendered.
This is a rather large issue, as the lists that the RecyclerView is showing can contain several hundred elements.
This is at the moment causing quite a lot of lag (obviously) as it has to render all views at once, and can't reuse any already inflated views as the RecyclerView normally does.
This is my current XML (Removed some bloat to minimize it):
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="90dp">
<!-- Some content -->
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Some more content -->
</LinearLayout>
<!-- Product list -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"/>
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
This is my onCreateView() from the Fragment that is inflating the view containing the NestedScrollView and RecyclerView:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.category_content_fragment, container, false);
ButterKnife.bind(this, root);
List<Product> products = new ArrayList<>(); //This is populated by other means, not relevant to the issue
productsRecyclerView.setNestedScrollingEnabled(false);
productsRecyclerView.setHasFixedSize(true);
productsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
ProductsContentAdapter productsContentAdapter = new ProductsContentAdapter(products);
productsRecyclerView.setAdapter(productsContentAdapter);
return root;
}
I have seen this post about the issue:
How to put RecyclerView inside NestedScrollView?
But it doesn't mention a final fix to the issue sadly.
To clarify:
The RecyclerView scrolls perfectly, it shows at the correct time, but the issue is that it renders ALL of its children instantly, meaning possible several hundreds of elements, even though the screen only shows 5-6 at a time at max.
Please feel free to ask questions if more information is needed.
------- EDIT -------
After many failed attempts of other solutions, i ended up using Jeeva Nandhan's solution.
Prior to asking this question i knew that was a possible solution, but i had 11 different possible views that needed to fit into the RecyclerView, so i would've liked to avoid it.
After using different ViewTypes, it worked perfectly. I was afraid it would be very inefficient due to the high amount of ViewTypes, but it's buttery smooth.
I too have come across this issue... This is because both scrollview and RecyclerView are different in loading data, since the ScrollView acts as the parent in this case and we are using the below line in our code.
setNestedScrollingEnabled(false);
This will make the scroll slow and hang issue based on the Recyclerview data.
One way which I have used to solve this issue is adding header to the Recyclerview..
Here I'll explain it clearly.
lets assume this recyclerview is in our activity.
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
The adapter class will be like this, where we will add the header
public class SampleAdapter extends RecyclerView.Adapter {
private final int BODY = 1;
private final int HEADER = 2;
private List<String> data = null;
SampleAdapter(List<String> data) {
this.data = data;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return HEADER;
}
return BODY;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case HEADER:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.inflate_header_layout, parent, false);
return new HeaderViewHolder(view);
default:
//Setting the Body view...
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.inflate_details, parent, false);
return new BodyViewHolder(view);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof BodyViewHolder) {
//Set the body content....
if (data != null && data.size() > 0) {
/** Since we have added one cell for header,
* we need to decrement the position and render the body view.
*
*/
int bodyPosition = position - 1;
}
} else if (holder instanceof HeaderViewHolder) {
//Set the header content...
}
}
#Override
public int getItemCount() {
//Sice we are going to add header, we are supposed increase the count by one...
return data.size() + 1;
}
}
by this there is no need for NestedScrollView and all the view will work in RecyclerView behavior...
Hope this is helpful :)
If you have large amount of data to display,show only some numbers of data first time than on scroll use loadMoreListener to get next data.
It is my third day now dealing with the handling of my view clicks. I originally was using ListView, then I switched to RecyclerView. I have added android:onclick elements to every control on my row_layout and I am handling them in my MainActivity like this:
public void MyMethod(View view) {}
In my old ListView implementation, I have done setTag(position) to be able to get it in MyMethod by doing this inside it:
Integer.parseInt(view.getTag().toString())
This worked nicely without problems. Though now I am dealing with RecyclerView and being forced to use the ViewHolder, which does not offer a setTag method. After searching for 2 hours, I have found that people use setTag like this:
holder.itemView.setTag(position)
This was acceptable. Though when I try to get the value from the MyMethod function using the line:
Integer.parseInt(view.getTag().toString())
The application crashes. I have read several implementation of onclick handling inside the adapter which works but I have to use the MainActivity because I am using something that is unique to that activity.
TL;DR I want to send the position of the clicked row to my MainActivity in a simple manner.
Edit: I apologize for the confusion since my topic was not very thorough. I have a RecyclerView and an adapter. The adapter is linked to my row_layout. This row_layout xml has one root LinearLayout. Inside it there is one TextView, another LinearLayout (which has two TextViews) and one Button (for simplicity). I do not want to suffer for dealing with the clicks on RecylerView like I did with the ListView. So, I have decided to add an android:onclick for every control, then link TextView and LinearLayout to a single method and link the Button (and future Buttons) to their unique methods. What I am missing is that I want to be able to tell the position on each of the receiving methods on my MainActivity. If I must link everything that comes from the adapter and goes into the MainActivity to a single onclick handler, so be it. Although, how would I tell which control fired the click?
Edit 2: The requested layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:onClick="MyMethod"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1">
<TextView
android:id="#+id/letter"
android:onClick="MyMethod"
android:layout_width="60dp"
android:layout_height="fill_parent"
/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:onClick="MyMethod"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/firstname"
android:onClick="MyMethod"
android:layout_width="fill_parent"
android:layout_height="17dp" />
<TextView
android:id="#+id/longname"
android:onClick="MyMethod"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:text="Test"
android:onClick="OtherMethod"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="#+id/process"/>
</LinearLayout>
You can achieve this by creating an interface inside your adapter for an itemclicklistener and then you can set onItemClickListener from your MainActivity.
Somewhere inside your RecyclerViewAdapter you would need the following:
private onRecyclerViewItemClickListener mItemClickListener;
public void setOnItemClickListener(onRecyclerViewItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
public interface onRecyclerViewItemClickListener {
void onItemClickListener(View view, int position);
}
Then inside your ViewHolder (which I've added as an inner class inside my adapter), you would apply the listener to the components you'd like the user to click, like so:
class RecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageView imageview;
RecyclerViewHolder(View view) {
super(view);
this.imageview = (ImageView) view
.findViewById(R.id.image);
imageview.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onItemClickListener(v, getAdapterPosition());
}
}
}
This example shows an onClickListener being applied to the image inside a ViewHolder.
recyclerView.setAdapter(adapter);// set adapter on recyclerview
adapter.notifyDataSetChanged();// Notify the adapter
adapter.setOnItemClickListener(new RecyclerViewAdapter.onRecyclerViewItemClickListener() {
#Override
public void onItemClickListener(View view, int position) {
//perform click logic here (position is passed)
}
});
To implement this code, you would setOnItemClickListener to your adapter inside MainActivity as shown above.
EDIT
Because the View is getting passed into the OnItemClickListener, you can perform a switch statement inside the listener to ensure that the right logic is being performed to the right component. All you would need to do is take the logic from the MyMethod function and copy and paste it to the component you wish it to be applied to.
Example:
recyclerView.setAdapter(adapter);// set adapter on recyclerview
adapter.notifyDataSetChanged();// Notify the adapter
adapter.setOnItemClickListener(new RecyclerViewAdapter.onRecyclerViewItemClickListener() {
#Override
public void onItemClickListener(View view, int position) {
Switch (view.getId()) {
case R.id.letter:
//logic for TextView with ID Letter here
break;
case R.id.firstname:
//logic for TextView with ID firstname here
break;
....
//the same can be applied to other components in Row_Layout.xml
}
}
});
You would also need to change something inside the ViewHolder. instead of applying the OnClickListener to an ImageView, you would need to apply to the whole row like so:
RecyclerViewHolder(View view) {
super(view);
this.imageview = (ImageView) view
.findViewById(R.id.image);
view.setOnClickListener(this);
}
EDIT 2
Explanation:
So, with every RecyclerView. You need three components, The RecyclerView, RecyclerViewAdapter and the RecyclerViewHolder. These are what define the actual components the user sees (RecyclerView) and the Items within that View. The Adapter is where everything is pieced together and the Logic is implemented. The ins and outs of these components are nicely explained by Bill Phillips with the article RecyclerView Part 1: Fundamentals For ListView Experts over at Big Nerd Ranch.
But to further explain the logic behind the click events, it's basically utilizing an interface to pass information from the RecyclerViewAdapter to the RecyclerViewHolder to your MainActivity. So if you follow the life-cycle of the RecyclerView adapter, it'll make sense.
The adapter is initialized inside your MainActivity, the adapter's constructor would then be called with the information being passed. The components would then be passed into the adapter via the OnCreateViewHolder method. This itself tells the adapter, that's how you would like the list to look like. The components in that layout, would then need to be individually initialized, that's where the ViewHolder comes into play. As you can see like any other components you would initialize in your Activities, you do the same in the ViewHolder but because the RecyclerViewAdapter inflates the ViewHolder you can happily use them within your adapter as shown by Zeeshan Shabbir. But, for this example you would like multiple components to have various logic applied to each individual one in your MainActivity class.
That's where we create the click listener as a global variable (so it can be accessed by both the ViewHolder and the Adapter) the adapter's job in this case is to ensure the listener exists by creating an Interface you can initialize the listener through.
public interface onRecyclerViewItemClickListener {
void onItemClickListener(View view, int position);
}
After you've defined the information you would like the interface to hold (E.G. the component and it's position), you can then create a function in which the adapter will call to apply the logic from your Activity (same way you would called View.OnClickListener) but by creating a setOnItemClickListener, you can customize it.
public void setOnItemClickListener(onRecyclerViewItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
This function then needs onRecyclerViewItemClickListener variable passed to it, as seen in your MainActivity. new RecyclerViewAdapter.onRecyclerViewItemClickListener() in this case it's the interface you created before with the method inside that would need to be implemented hence the
#Override
public void onItemClickListener(View view, int position) {
}
is called.
All the ViewHolder does in this scenario is pass the information (The components it's self and the position) into the onItemClickListener with the components attached (inside the onClick function) to finalize the actual click functionality.
if you would like me to update the explanation in anyway, let me know.
I think you are stuck with handling multiple clicks on ReceylerView if that is the case then let me share you the a code snippet from my project. That's how i handle the click in RecyclerView
public class ExploreItemAdapter extends RecyclerView.Adapter<ExploreItemAdapter.ViewHolder> {
private WSResponseDTO<Data> wsResponseDTO;
public ExploreItemAdapter(WSResponseDTO<Data> wsResponseDTO) {
this.wsResponseDTO = wsResponseDTO;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.consumer_dashboard_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
holder.tvCompanyName.setText(wsResponseDTO.getData().getPosts().get(position).getStoreInfo().getStoreName());
holder.tvBranchName.setText("(" + wsResponseDTO.getData().getPosts().get(position).getStoreInfo().getBranchName() + ")");
holder.tvLikes.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getFavoriteCount() + "");
holder.tvShares.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getShareCount() + "");
holder.validity.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getValidFrom() + "-" +
wsResponseDTO.getData().getPosts().get(position).getPost().getValidTo());
holder.sTime.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getTime());
holder.tvTitle.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getHeading());
holder.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(view.getContext(), "card is touched", Toast.LENGTH_SHORT).show();
view.getContext().startActivity(new Intent(view.getContext(), ConsumerDetailOfferActivity.class).putExtra("post", wsResponseDTO.getData().getPosts().get(position)));
}
});
}
#Override
public int getItemCount() {
return wsResponseDTO.getData().getPosts().size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.card_explore)
CardView cardView;
#BindView(R.id.tv_company_name)
TextView tvCompanyName;
#BindView(R.id.tv_branch_name)
TextView tvBranchName;
#BindView(R.id.tv_title)
TextView tvTitle;
#BindView(R.id.tv_like_count)
TextView tvLikes;
#BindView(R.id.tv_share_count)
TextView tvShares;
#BindView(R.id.tv_validity)
TextView validity;
#BindView(R.id.tv_sTime)
TextView sTime;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
you can set click listener to any item in bindViewHolder() like i did for cardView I hope this will help some.
I have 3 different viewgroups that need to be added in a LinearLayout. I'm using addView() to add it.
However, the adding is based on the response that my web service is returning. If there is no data, it will make a callback to the UI that the view will be empty.
Essentially, there are 3 views which are Featured, Latest and Categories. I want Featured to be at the top, followed by Latest and Categories.
I'm calling the web service like so,
public void loadFromApis() {
dealsService.getFeaturedDeals(this);
dealsService.getLatestDeals(this);
dealsService.getDealsCategories(this);
}
Example of successful callback (with data) and view adding:
#Override
public void onFeaturedSuccess(List<FeaturedModel> model) {
View view1 = DealsPanel.build(this, model);
linearLayout.addView(view1, 0);
}
#Override
public void onLatestSuccess(List<LatestModel> model) {
View view2 = DealsPanel.build(this, model);
linearLayout.addView(view2, 1);
}
#Override
public void onCategoriesSuccess(List<CategoriesModel> model) {
View view3 = DealsPanel.build(this, model);
linearLayout.addView(view3, 2);
}
I've tried using the index parameter to set the position, but since I'm loading the view based on API response, the layout wouldn't know which view to be draw first, so initializing the index would result in IndexOutOfBoundsException error.
My question is, based on this requirements, how can I statically define the position of each view to be added first and so forth? Any suggestions on improving the structure of this code?
Thanks in advance
One approach would be to statically define 3 child layouts in code or in XML inside of your parent LinearLayout, and then add your new views to the child layouts. That will preserve their order. For example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout android:id="#+id/featuredDealsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout android:id="#+id/latestDealsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout android:id="#+id/dealCategoriesLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Then, assuming you initialize variables containing the new layouts (ie featuredDealsLayout) you could change your code to something like:
#Override
public void onFeaturedSuccess(List<FeaturedModel> model) {
View view = DealsPanel.build(this, model);
featuredDealsLayout.addView(view);
}
#Override
public void onLatestSuccess(List<LatestModel> model) {
View view = DealsPanel.build(this, model);
latestDealsLayout.addView(view);
}
#Override
public void onCategoriesSuccess(List<CategoriesModel> model) {
View view = DealsPanel.build(this, model);
dealCategoriesLayout.addView(view);
}
Let's say I have a custom view:
<com.xx.xx.xx.CustomView
android:id="#+id/customView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doAction"/>
Then in my Activity I have:
public void doAction(View v) {
if(customView == null) customView = (CustomView) v;
... do stuff
}
So as you can see, first time I click the view, I retrieve it and store it in an Activity field. Then I can use it anywhere. With this, I don't need to use findViewById.
I have to questions:
Is this more performant or is the same?
Is this a correct way to go?
Many thanks!
I am working through the Big Nerd Ranch guide for android programming, and I am at the challenge for Chapter 16. The challenge is to make an EmptyView for a ListView, and then make a button on the EmptyView that adds stuff. I got the EmptyView to work but I can't figure out where I should make my button. Here is my code.
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v= super.onCreateView(inflater, parent, savedInstanceState);
inflater.inflate(R.layout.list_frame_layout, parent);
return v;
}
and here is my XML.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="#android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ListView>
<LinearLayout android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="#string/empty_no_crime" />
<Button
android:id="#+id/empty_new_crime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/empty_new_crime">
</Button>
</LinearLayout>
</FrameLayout>
The book is telling us to use fragments, hence the inflate. I figure the code should be
mNewCrime=(Button)getView().findViewById(R.id.empty_new_crime)
but that isn't working. Any ideas?
Edit*: Hmmm, apparently this also really isn't working that well. When I do add stuff, the EmptyView does not go away, it just gets pushed down while items are listed. Any ideas on how to make the EmptyView go away as soon as I add things?
I had trouble with this challenge at first as well. I over thought it! You have probably solved this issue by now but I thought it would be useful to post an answer for others. The following worked for me:
Create a new XML file specifying the "empty" and "list" views as you have done already.
Modify your existing onCreateView method to inflate the new modified layout which contains the "empty" and "list" views you have defined in your XML.
Create a new button and setup the onClickListener for the button.
Here is the code:
#TargetApi(11)
#Override
// We override the onCreateView to set the subtitle by default if we are rocking >3.0
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
super.onCreateView(inflater, parent, savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
if(mSubtitleVisible){
getActivity().getActionBar().setSubtitle(R.string.subtitle);
}// End inner if
}// End if
View v = inflater.inflate(R.layout.empty_layout, parent, false);
mNewCrimeButton = (Button)v.findViewById(R.id.add_crime);
//Define an click event listener for the button and launch the new crime fragment when clicked
mNewCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Crime crime = new Crime();
//Get the crimelab from the activity and add the crime
CrimeLab.get(getActivity()).addCrime(crime); //getActivity returns the activity this fragment is attached to
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
startActivityForResult(i,0);
}//End onClick
});
return v;
}// End onCreateView
This should work with your existing xml layout. I hope this helps.
I too struggled initially with this, essentially solving it the same way the above poster did. However my problem was a bit different. I was getting bombed out of the application on startup, because my code that set up the onClick listener looked like this:
Button mCrimeButton = (Button)v.findViewById(R.id.crime_button);
mCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
initiateCrimeRecord();
}
});
It wasn't until I moved the declaration of mCrimeButton up to the class level making it an instance variable of the class that I was able to successfully execute the app:
public class CrimeListFragment extends ListFragment {
private static final String TAG = "CrimeListFragment";
private ArrayList<Crime> mCrimes;
private boolean mSubtitleVisible;
private Button mCrimeButton;
*
*
*
#TargetApi(11)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_empty_crime_list, parent, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if ( mSubtitleVisible) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
} else {
getActivity().getActionBar().setSubtitle(null);
}
}
// Set the button up on the empty view
mCrimeButton = (Button)v.findViewById(R.id.crime_button);
mCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
initiateCrimeRecord();
}
});
return v;
}
I then went back and noticed that in all the other examples in the book, the widgets that get manipulated are declared as private instances of the class. Why is this? Android doesn't allow you to just get a local instance to attach the listener?