Is this more performant than findByViewId? - android

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!

Related

Pass data from RecyclerView Adapter to Parent Fragment

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.

Multiple clickable views inside listview item

I am trying to create an item in a ListView that has multiple options; view and edit. I would like to create it in exactly the same way as android's contact system - see below:
I have added the red boxes to illustrate the behaviour I want. If you press within the left red-box, you call the contact. If you press within the right red-box, you send a text message to the contact. I have already created a similar layout in XML, but I am having trouble implementing this functionality in code.
I have tried to create custom android:onClick function calls for the separate layouts within the item, but calling an onClick method only allows you to pass in the View as a parameter, but not the position. Needing the position to use listview.getItemAtPosition function, I tried to use listview.getPositionForView to return the position but found this was extremely unstable and was very easy to return incorrect positioning due to recycling of views.
I then tried to set the item's position as the 'tag' in the getView method of my adapter, like so: convertView.setTag(position). But on the onClick method of my activity, I try and use getTag and cast it back to an integer, and it always returns null, which I find puzzling.
What is the best way of implementing a list populated by items with multiple buttons/layouts on each item?
You can create an onClick event on each views in your row like this :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/text_id"
android:layout_width="0sp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:onClick="textOnClickEvent"/>
<ImageButton
android:id="#+id/button_id"
android:layout_width="#dimen/width_button"
android:layout_height="match_parent"
android:onClick="imageOnClickEvent"
android:src="#android:drawable/ic_menu_delete" />
</LinearLayout>
Or even, add onClick listeners on each views in the getView method...
more info on this here.
In the list view when you define getview method, this is where you provide all the details of the single list item. There you can mention onlick event of each of the views.
in adapter class, add View.OnClickListener to the getView method:
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
if(view == null) {
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
view = layoutInflater.inflate(R.layout.grid_vendor_item, null);
}
final TextView textName = (TextView) view.findViewById(R.id.text_id);
final ImageButton imageProfil = (TextView) view.findViewById(R.id.button_id);
textName.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// add your edit codes
}
});
imageProfil.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// add your open prodil codes
}
});
return view;
}

Custom Android ListView

I'm new to the this excellent site and in the Java programming Android. I started to make a small test application for the listing of my favorite places in my town. I tried to follow some tutorials on different pages, but when I do I put in my project in Eclipse always gives me more than a million mistakes although imports classes and other methods.
I want to build on the example image discotheque next to the image name discos and under that name Diskotek smaller text additional info.
It would be really grateful for all the help
A Custom ListView requires total 4 basic stuff 2 in design (layout .xml) & 2 class (.java)
2 Layout
a) Basically a container which has a listview with either a heading or button depends on you
b) How each row should look like should they have buttons, images, textview how ever you want.
2 Java Class File
a) One is the Activity which you will have definitely.
b) Custom Adapter which says which value from your Activity will go to which View (Button , image ) depending on your requirement.
The best example is to follow this Tutorial
The first thing I think when someone says 'I tried to follow some tutorials...' but they don't work, It gets kind of hard to believe.
Where's the code you tried?
What are the import errors on the editor?
That'd be an easier problem to address.
To give you a simple ListView example:
First, create a resource file at your preference: (example.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/disco_image"
android:layout_alignParentTop="true"
android:src="#drawable/ic_home"
android:layout_width="96dp"
android:layout_height="96dp"/>
<TextView
android:id="#+id/disco_title"
android:padding="12dp"
android:layout_toRightOf="#+id/disco_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/disco_info"
android:padding="12dp"
android:layout_toRightOf="#+id/disco_image"
android:layout_below="#+id/disco_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
Then, create a custom adapter, they are very simple. Just extend a BaseAdapter for now.
public class ExampleAdapter extends BaseAdapter {
//Let's create some constants first, to fill out the rows
private static final String [] DISCO_NAMES = {"Disco One", "Disco Two", "Disco Three", "Disco Four"};
private static final String [] DISCO_INFO = {"Some Info One", "Some Info Two", "Some Info Three", "Some Info Four"};
private LayoutInflater mInflater;
//Our custom adapter needs a constructor, so you can create from your activity.
public ExampleAdapter (final Context context) {
//for now, let's just get the context, we'll need it to inflate views
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
//this needs to return to the amount of rows you want to display.
//right now we return a fixed value, this could vary based on your needs
return DISCO_NAMES.length;
}
#Override
public Object getItem(int pos) {
//this is useful for knowing what item is at what position
//for now, let's just return the disco name shall we?
return DISCO_NAMES[pos];
}
#Override
public long getItemId(int pos) {
//This returns an id to the item
//personally I don't use this, so you can just return the position
return pos;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
//Ha, here's the important part
//ListViews reuse rows, so let's check if the view (also known as convertview) is new or being reused
if (view == null) {
//this means it's a new view, so we need to inflate it
view = mInflater.inflate(R.layout.example, null);
}
((TextView) view.findViewById(R.id.disco_title)).setText(DISCO_NAMES[position]);
((TextView) view.findViewById(R.id.disco_info)).setText(DISCO_INFO[position]);
//You can also set some images to the imageview on the layout we created earlier
return view;
}
}
Then, let's create a ListActivity for example purposes. NOTE ListActivit does not require a Layout Resource to be set via setContentView, so we don't call it here.
public class ExampleListActivity extends ListActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//create the adapter
ExampleAdapter mExampleAdapter = new ExampleAdapter(this);
//fill the listView
setListAdapter(mExampleAdapter);
}
}
That should compile as is, but you may want to look into ViewHolder patterns and blah blah blah for performance reasons. Obviously you need to read more, but I hope this helps as a starting point.

Why EditText in a custom compound view is re-using the text entered in another compound view instance?

I'm trying to write a custom compound view composed by a TextView and an EditText, _compound_view.xml_:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundText"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/textLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Label" />
<EditText
android:id="#+id/textEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="enter text here" >
</EditText>
and this is the class extending LinearLayout:
public class CompoundView extends LinearLayout {
public CompoundView(Context context, AttributeSet attrs) {
super(context, attrs);
readAttributes(context, attrs);
init(context);
}
public CompoundView(Context context) {
super(context);
init(context);
}
private void init(Context c) {
final LayoutInflater inflater = (LayoutInflater) c
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.compound_view, this);
}
}
Now, if I use 2 of these View in my _activity_main.xml_:
<RelativeLayout 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=".MainActivity" >
<it.moondroid.compoundview.example.CompoundView
android:id="#+id/compoundview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
<it.moondroid.compoundview.example.CompoundView
android:id="#+id/compoundview2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/compoundview1" />
</RelativeLayout>
and in the Activity code I only inflate the RelativeLayout, without managing onSaveInstanceState:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
then when I write something in the 2nd EditText and I rotate my device, the same text appears in the EditText of the first custom View.
Why is happening this behaviour?
EDIT:
I solved the issue by removing android:id and using android:tag for the EditText in compound_view.xml, then managing the saving of the EditText state in CompoundView class:
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("instanceState", super.onSaveInstanceState());
bundle.putString("currentEdit", mEditText.getText().toString());
bundle.putBoolean("isFocused", mEditText.hasFocus());
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mEditText.setText(bundle.getString("currentEdit"));
if (bundle.getBoolean("isFocused")) {
mEditText.requestFocus();
}
super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
return;
}
super.onRestoreInstanceState(state);
}
You need to disable SaveEnabled property of EditText using android:saveEnabled="false"
In your custom view, you are inflating layout from XML which has ID defined. Android OS has default functionality to save and restore the state of the view if the view has ID defined.
It means that it will save the text of the EditText when Activity gets paused and restore automatically when Activity gets restored. In your case, you are using this custom view multiple times and that is inflating the same layout so your all EditText have the same ID. Now when Activity will get pause Android will retrieve the value of the EditText and will save against their ID but as you have the same ID for each EditText, values will get override and so it will restore same value in all your EditText.
I'll start off by saying that I haven't confirmed this... but I experienced the same issues when using a compound view, similar to what you were doing.
I think the root of the problem is how Android automatically saves the state of EditText controls, and I think it saves it by "id". So if you have multiple controls in your xml with same "id", then when it saves state, and then restores state, all controls with the same id get the same value. You can try this by adding 2 EditText contols to you normal xml and give them the same android:id value and see if they end up getting the same value.
In your case, you can try to NOT use ids in the compound view and rather find the elements another way, either by tag (View.findViewWithTag), or by name, and see if that makes a difference.
In my case, I solved the problem by doing the latter.
I had the same issue, This is how I made it to work. First need to set false for saveEnabled for editText. We can keep android:id in our layout.
<EditText
android:id="#+id/editText"
android:saveEnabled="false"
Then override below methods in your compound view and manage state by your own. Feel free to ask working example if needed.
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("state", super.onSaveInstanceState());
String text = editText.getText().toString();
bundle.putString("text", text);
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
String text = bundle.getString("text");
state = bundle.getParcelable("state");
editText.setText(text);
}
super.onRestoreInstanceState(state);
}
Take a look at my comment in your question and also make sure that you're getting correctly the references to your views.
I'm using your code like this:
CompoundView cv1 = (CompoundView) findViewById(R.id.compoundview1);
TextView tv1 = (TextView) cv1.findViewById(R.id.textEdit);
CompoundView cv2 = (CompoundView) findViewById(R.id.compoundview2);
TextView tv2 = (TextView) cv2.findViewById(R.id.textEdit);
In case you have more complex compound view with many child view, you can consider overriding the dispatchSaveInstanceState of your most outer ViewGroup class and don't call the super implementation.
Like this:
#Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//If you don't call super.dispatchRestoreInstanceState(container) here,
//no child view gets its state saved
}
I use this, because in my case I have hierarchy of many different compound views that have common super class in which I did this override. (I kept forgetting to set the SaveEnabled attribute to false for new layouts.) However there are many caveats, for example you need to manually save focused view and then request its focus, so your app doesn't behave oddly when screen is rotated.
EDIT:
If you actually need to save state of your compound view, overriding dispatchSaveInstanceState with an empty body will cause onSave/RestoreInstanceState not being called. If you want to use them and still not save state of any of the child views, you need to call super.dispatchFreezeSelfOnly(container) in your override.
More on this here: http://charlesharley.com/2012/programming/views-saving-instance-state-in-android
The issue is happening because of the id field on compound_view.xml
From your code, I just noticed that you inflated compound_view layout file in CompoundView class.
As soon as you create compound_view.xml and put android:id="#+id/textLabel" and android:id="#+id/textEdit" id in your layout xml file, android studio automatically create those ids into int values in R class for single time.
So, when you put CompoundView twice time in your activity_main.xml layout file, you just creating two instance of CompoundView but, both instances textEdit and textLabel have only 1 address location for each one. So, they are pointing to same address locations which are declared in R class.
That's why, whenever you change textEdit or textLabel text programatically, they also change other textEdit or textLabel which are presented in both of your CompoundView
I would like to emphasize a great article, which opened my eyes. It is based on reimplementing onSaveInstanceState() and onRestoreInstanceState(state: Parcelable?).
The advantage of this is that you can use the same compound view multiple times in the same layout (no duplicate ids problem).
In case someone has troubles with incorrect focus being restored after screen rotation, which occurs due to the shared ids of inner views, you can control which id is saved as focused view by overriding findFocus method like this:
override fun findFocus(): View {
if (focusedChild != null) {
return this
}
return super.findFocus()
}
Then the focus gets restored to your compound view, however you should handle the requestFocus call, so the proper child view gets focus upon restoration.
I got the same annoying problem and solved it with another solution than the ones suggested. When the custom view is inflated, I don't find them with IDs or tags, I get them using the getChildAt method. I did not have to save the instance state.
Here is an example:
Custom XML to inflate (custom_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"/>
</merge>
You then just get the views this way during the view initialization:
private void initView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_view, this, true);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER);
TextView mTitleTV = (TextView) getChildAt(0);
EditText mContentET = (EditText) getChildAt(1);
}
IMPORTANT: you must remove all IDs from the XML

Android: setContentView() == getViewInflate().inflate()?

I try my hardest to develop a clever way to clean out piles of Blah blah = (Blah) this.findViewById(R.id.blah) that otherwise pollute the field and the onCreate() method of my little Activity, and to do so, I feel I should not use setContentView() but getViewInflate().inflate() for every View defined in XMLs.
Is Activity.setContentView() is sorta a syntax sugar and it's virtually repeating getViewInflate().inflate() for every View on XML? I read something saying as if they were the same.
If I can get an answer by looking into the code, please tell so. I checked Activity.class, but only comments were found.
The setContentView on your Activity actually calls the setContentView on the Window used by the activity, which itself does a lot more than just inflating the layout.
What you could do is to map the views to the class field using reflexion. You can download a utility class on Github that does this.
It will parse all the views declared in the layout, then try to find the name corresponding to the id in your R.id class. Then it will try to find a field with the same name in the target object and set it with the corresponding view.
For example, if you have a layout like this
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
it will automatically map it to a textView1 field in your activity.
I'm posting my poor research. In summary, Activity.setContentView() delegates PhoneWindow.setContentView() (the only concrete class of Window ) within which LayoutInflater.inflate() is called, so saying "setContentView() == ViewInflate().inflate()" is not so overly off, I guess.
public class Activity extends ContextThemeWrapper{
private Window mWindow;
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
public Window getWindow() {
return mWindow;
}
}
public class PhoneWindow extends Window {
private LayoutInflater mLayoutInflater;
#Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
**mLayoutInflater.inflate(layoutResID, mContentParent);**
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
}
Actually you're right, there's two ways to achieve the same thing:
1) setContentView(R.layout.layout);
2)
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.layout, null);
setContentView(v);
You decide what is more appropriate for you. Hope this helps.

Categories

Resources