Using a class to set up a view in android - android

I am writing an android application where the user can add and remove fields interactively. Each field the user add has some buttons, and value which the user should be able to interact with. I thought to create a subclass to handle the field I can add which will hold it's own onClickListener but I'm not sure how to do so.
Here is some pseudo code which should make my intention clear.
Say I have a class , vClass:
public class sClass extends View implements onClickListener{
this.setContextView(R.layout.vClass);//how do I do this in a correct way?
#Override
public void onClick(View v){ //add code here
}
}
and aClass which is the main class of the application.
public class aClass extends Activity implements onClickListener{
Button b;
LayoutInflater i;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.b =(Button)this.findViewById(R.id.btn);
b.setOnClickLister(this);
}
#Override
public void onClick(View v){
//this is what I have now to add View
final LinearLayout canvas =(LinearLayout)aClass.this.findViewById(R.id.main);
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
canvas.addView(cv);
}
}
how can I use the vClass to add elements to the aClass.
Typing this is I thought about another solution.
If I keep track of the id's of all the views I have added (without the subcomponents) can I do something of that kind:
View vv = findViewById(id);
Button bb = vv.findViewByIf(R.id.xmlId);
where id is an id I have assigned to the view which I know and xmlId is a string I have specified in the xml file?
Thanks
Yotam
For solution, read the discussion below

IDs used in layouts are not necessarily unique, so i guess you should keep the added Views in an ArrayList, as
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
this.viewList.add(cv);
canvas.addView(cv);
or you could declare an index member inside your sClass implementation, and store the added indices in an ArrayList:
private int index;
public sClass(final int index)
{
this.index = index;
}
public int getIndex()
{
return this.index;
}
#override
public boolean equals(Object obj)
{
return ((obj instanceof sClass) && (((sClass)obj).getIndex() == this.index));
}
Both ways you have access to the view you want.
The button that lays inside the view is accessible via the findViewById() method
Button bb = vv.findViewById(R.id.buttonId);
where R.id.buttonId was declared in the vv view's layout xml file, as follows:
<Button android:id="#+id/buttonId" [...] />

Related

Does Android keep the view object hierarchy in memory?

When I create a button:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout layout = …;
Button btn = new Button(this);
btn.setText("My Button");
layout.addView(btn);
}
If I don't keep a strong reference to the btn, does Android keep the btn instance alive?
For example, does layout.getChildView(0) return the exact instance (btn)? or does Android create and return a new instance of Button and return it?
I'm not talking about subclassing, (e.g. class MyButton extends Button) which I think it's obvious it must be kept in memory, I am only asking about built-in view classes.
ViewGroup stores strong references on its children in an array. If you take a look into ViewGroup sources you find following field:
private View[] mChildren;
And when you call getChildAt you get an instance from this array:
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
For any view group when you are adding any child view at a time it will not create new instance.

Android Listener example to hold current TextView elements on Fragment

I have a main Activity and several Fragments. On each fragment I have several TextView elements. I want to change font size of TextViews on the current displayed Fragment from Main Activity. Therefore I want to hold list of TextView elements on the currentFragment using a Listener.
But I don't know how to implement such Listener?
Is the listener right way to do that?
If there is a another way to achieve this, I wanted to know. Any answers welcome. Thanks.
If I got your question right, you could just access the current fragment's TextView object from the Main Activity and use the setTextSize() method.
You declare a list of TextViews and provide a method to add the textviews to the list.
And you also provide a method to signal the activation state. This method will go through the list of textviews calling the changes you need one by one.
From your main activity you will call fragment.changeListeningTextViews();
This goes in the Fragment:
private ArrayList<TextView> listeningTextViews;
public void addListeningTextview(TextVew tv){
//Here check if the text view is already added not to add it twice.
listeningTextViews.add(tv);
}
public void changeListeningTextViews(){
for(TextView tv : listeningTextViews){
tv.setFont(...);
//What you want called on each TextVeiw
}
}
In onCreateView you add the text views to the list:
TextView textView1 = (TextView) rootView.findViewById(R.id.aview);
addListeningTestview(textView1);
TextView textView2 = (TextView) rootView.findViewById(R.id.anotherview);
addListeningTestview(textView2);
...
Note: If it were the other way round when you need the Main Activity to listen on events coming from the fragment the solution would be different and the it would involve a custom Listener interface to be implemented by the Main Activity.
I had implemented as below. It works well fine but I don't know how clear solution it is.
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action buttons
switch(item.getItemId()) {
case R.id.smallFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize1);
return true;
case R.id.mediumFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize2);
return true;
case R.id.largeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize3);
return true;
case R.id.extraLargeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize4);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
public class FontSizeHelper {
public static List<View> viewElements = new ArrayList<>();
public static void initFontElements(){
viewElements = new ArrayList<>();
}
public static void addFontEelements(View view){
viewElements.add(view);
}
public static void updateFontSize(int fontSize){
for(View v : viewElements){
if(v instanceof TextView){
((TextView) v).setTextSize(fontSize);
}
}
}
}
public class FragmentA extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.a_fragment, container, false);
//initialize empty list for View objects.
FontSizeHelper.initFontElements();
//add View object/TextViews into list
FontSizeHelper.addViewEelements(textView1);
FontSizeHelper.addViewEelements(textView2);
FontSizeHelper.addViewEelements(textView3);
//..etc
}}

Databinding apply for one layout used by multiple activity/fragment

I am replacing existing code by databinding. But I face a problem.
I have some layout files shared by more than one activity/fragment. E.g there is a layout file layout_sub used by SubFragmentA and its extending class SubFragmentB. And the data model used in these two fragment are not the same.
The code looks like following.
public class SubFragmentA extends Fragment {
private DataA dataA;
#Override
public View onCreateView(Bundle Bundle) {
View v = LayoutInflator.from(getActivity()).inflate(R.layout.shared_layout);
initView(v, dataA);
return v;
}
private void initView(view v, DataA dataA) {
// use dataA to init v
}
}
public class SubFragmentB extends Fragment {
private DataB dataB;
#Override
public View onCreateView(Bundle Bundle) {
View v = LayoutInflator.from(getActivity()).inflate(R.layout.shared_layout);
initView(v, dataB);
return v;
}
private void initView(view v, DataB dataB) {
// use dataB to init v
}
}
So far, I think using DataA and DataB in layout_sub file at the same time is not a good idea, because it would require a lot of redundant code to decide which object to be used.
Please share your ideas on this problem.
Finally, I got a solution. The databinding is used for MVVM pattern. That means one layout corresponds to one ViewModel. And the ViewModel contains every data for UI layout. So I should prepare one ViewModel for each layout file. And every fragment/activity should just handle the ViewModel.

How to handle card's button OnClick event in GridView? Best practice

I'm trying to find the best solution to handle OnClick event, which generates by my card's button (see the picture bellow) within GridView.
So as you can see, I have just a normal GridView with cells made of my custom Card.
I just initialize GridView and it's adapter:
mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);
As you probably know I can easily handle OnClick events generated by GridView. But it will work only if I click on the card itself:
mGrid.setOnItemClickListener(..blah blah blah..);
I want to build something similar to this (see code bellow), so I can easily "implement" my Activity to handle my card's button OnClick event:
mGrid.setOnItemButtonClickListener(..blah blah blah..);
What is the best (clean\easy\elegant) way to do this?
Any help is truly appreciated. Alex. P.S. Sorry for my English:)
Since you want to dispatch to your activity, I would recommend exposing a method in the activity and call it directly from your click listener. The shortest (and cleanest from my perspective):
in your Adapter, say ArrayAdapter
define to listen for clicks (to avoid multitude of anonymous listener instances)
dispatch a call directly to your activity (since every view context is an activity)
context above can be treated as your ApplicationActivity only if you didn't manually provide some other context, say application context
private final MyAdapter extends ArrayAdapter implements View.OnClickListener {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(this);
return card;
}
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing()) {
applicationActivity.onCardButtonClick();
}
}
}
// in your ApplicationActivity
public final class ApplicationActivity extends Activity {
...
public void onCardButtonClick() {
// deal with your click
}
}
There are other, textbook options (setting a listener, or activity in your view creation and so forth) but I avoid them since they don't solve absolutely anything.
They just add more dust in your code.
Any View context defined properly points to the activity (since it is a context too) which holds all view structure. This way you can access your activity quick and relatively easy.
BTW Event bus is not a good option since event buses are great for one-to-many relations (one dispatcher, many listeners) but add more complexity when used intensively for one-to-one calls (dispatcher-listener)
Addition for the comment
You can tweak a little the code and rather using the adapter, you can dispatch directly from your cell. In other words rather using the adapter as a delegate, create an anonymous listener and then reach and call the activity directly from your card button click:
public final MyAdapter extends ArrayAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
applicationActivity.onCardButtonClick();
}
}
});
return card;
}
}
Addition for the comment - Compound View
To encapsulate all cell logic, you can create a custom view from scratch or use a compound view. The example below is using a compound view:
public class ApplicationActivity extends Activity {
....
public void onCardButtonClick(Cell cell) {
// do whatever you want with the model/view
}
}
// ViewModel instances are used in your adapter
public final class ViewModel {
public final String description;
public final String title;
public ViewModel(String title, String description) {
this.title = title != null ? title.trim() : "";
this.description = description != null ? description.trim() : "";
}
}
public final class Cell extends LinearLayout {
private View button;
private ViewModel model;
// ViewModel is data model and is the list of items in your adapter
public void update(ViewModel model) {
this.model = model;
// update your card with your model
}
public ViewModel getModel() {
return model;
}
#Override
protected void onAttachedToWindow() {
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (model != null && activity != null && !activity.isFinishing() && !activity.isDestroyed() {
activity.onCardButtonClick(Cell.this);
}
}
});
}
}
// then your adapter `getView()` needs to inflate/create your compound view and return it
public final MyAdapter extends ArrayAdapter {
private final List<ViewModel> items;
public MyAdapter() {
// update your models from outside or create on the fly, etc.
this.items = ...;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// inflate - say it is a layout file 'cell.xml'
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell);
}
((Cell) convertView).update(items.get(position));
return convertView;
}
}
Adapter should handle this. Generally your Adapter should have method like setOnOptionsClickListener(OnOptionsClickListener listener) assuming that we are talking about ellipsis button.
So in your Activity/Fragment you use following code
public interface OnOptionsClickListener {
void onOptionsClicked(View view, PictureItem item);
}
mAdapter= new MyGridAdapter();
mAdapter.setOnOptionsClickListener(new OnOptionsClickListener() {
public void onClick(View view, PictureItem item) {
//process click
}
});
And following inside Adapter
public void setOnOptionsClickListener(OnOptionsClickListener l) {
mOnOptionsClickListener = l;
}
findViewById(R.id.btn_options).setOnClickListener(new OnClickListener(){
public void OnClick(View view) {
mOnOptionsClickListener.onOptionsClicked(view, currentPictureItem);
}
});
Please notice. You need to declare interface only if you need to have extra parameters in OnClick() method (for example currentPictureItem to get image url or item id). Otherwise, you can use just OnClickListener.
Edit
So here is explanation. Adapter serves like a View-provider for your GridView. It creates views and it configure it basic state. That's why all click listeners should be set in Adapter during views initializing. Moreover, we don't want to have a messy Activity with nested Adapter, but we want to have Adapter as a separate class. This is the reason you will usually need to create additional interface in order to have an access to currentItem object to extract data from.
Looks like nobody knows how to do this. So I found solution myself with help of #Dimitar G. and #Konstantin Kiriushyn. Thank you, guys.
1) I will create my own custom CardView using Compound View system, which will be pretty simple: LinearLayout + ImageView + TextView + Button.
public class TopicCardView extends LinearLayout {
private ImageView mImage;
private Button mButtonMenu;
private TextView mTitle;
public TopicCardView (Context context) {
initializeViews(context);
}
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.topic_card_view, this);
}
private void setTitle(...) {
...
}
private void setImage(...) {
...
}
private void setMenuClickListener(...) {
...
}
// and so on...
}
2) Then I will create method called createListOfGridCardsFromDB(...) in Activity\Fragment. It will generate list (LinkedList) of my custom CardViews (and it will also set titles\images and listeners to CardViews).
3) And then I will pass this generated LinkedList of my CardViews to GridViewAdapter.
This system makes able to use only one Adapter for all my card-grids in app. It also makes able to do nothing with clicks, interfaces, listeners and stuff in Adapter.

How to set a background drawable on a clicked GridView item in Android?

I have a GridView with a bunch of icons and I need to select one. And by select I mean:
I need the drawable id to store into a database (so I can access it later)
I need to draw some kind of visual cue on the grid to let the user know which icon is currently selected
I found this:
http://groups.google.com/group/android-developers/browse_thread/thread/f08a58167dbaa8c8
So I guess setSelection is out of the picture and I can't use it to select the icon itself nor draw the visual cue. I know the grid item position in the onItemClickListener and I can save it as a private class variable. My biggest problem is drawing that visual cue.
How can I achieve that? How can I draw a visual cue on the clicked item and if the user clicks different items, to "undraw" that visual cue and redraw it in the new item?
After tackling with this for a couple of hours I think I finally found the solution I was looking for. Although the answers are barely related, the initial edits on Ian solution helped me find this solution :)
I'm not going to explain everything I did, I think it's pretty self explanatory. Just a few remarks:
First I tried view.Invalidate() and view.postInvalidate() instead of iconAdapter.notifyDataSetChanged() but neither worked. The documentation stated that the invalidate methods only "asked" to redraw the view "when possible".
I was looking for a simpler solution than to merge two drawables. For instance, draw the icon on the ImageView background and the visual cue as the image. For some strange reason, the visual cue started to show randomly all over the other icons when the GridView was scrolled. I don't understand why, but the idea of a visual cue on top of a background image makes perfect sense to me and ImageView allows that, no need for that extra merge method. However, I couldn't make it work without it...
MyActivity.java
public class MyActivity extends Activity {
private GridView mGridViewIcon;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGridViewIcon = (GridView)findViewById(R.id.gridview_icon);
mGridViewIcon.setAdapter(new IconAdapter(this));
mGridViewIcon.setOnItemClickListener(new GridView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
IconAdapter iconAdapter = (IconAdapter)parent.getAdapter();
iconAdapter.setSelectedItemPosition(position);
iconAdapter.notifyDataSetChanged();
}
});
}
}
IconAdapter.java
public class IconAdapter extends BaseAdapter {
private int mSelectedPosition;
private Integer[] mThumbIds;
private int mIconSize;
private Context mContext;
public IconAdapter(Context context) {
mThumbIds = AppHelper.ICON_SET.keySet().iterator().next();
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.default_icon_size);
mContext = context;
}
#Override
public int getCount() {
return mThumbIds.length;
}
#Override
public Object getItem(int position) {
return mContext.getResources().getDrawable(mThumbIds[position]);
}
#Override
public long getItemId(int position) {
return mThumbIds[position];
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if(convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(mIconSize, mIconSize));
} else {
imageView = (ImageView)convertView;
}
if(mSelectedPosition == position) {
imageView.setImageDrawable(mergeDrawableLayers(mThumbIds[position],
R.drawable.ic_note_selected_mark));
} else {
imageView.setImageResource(mThumbIds[position]);
}
return imageView;
}
public void setSelectedItemPosition(int position) {
mSelectedPosition = position;
}
private Drawable mergeDrawableLayers(int background, int overlay) {
Drawable[] drawableLayers = new Drawable[2];
drawableLayers[0] = mContext.getResources().getDrawable(background);
drawableLayers[1] = mContext.getResources().getDrawable(overlay);
return new LayerDrawable(drawableLayers);
}
}
I believe, that if you want some kind of selection cue, you need a focusable object. However, with a focusable object (such as a Button), attaching OnItemClickListener to the GridView does not work (if i remember correctly). Rather, you must individually attach an OnClickListener to each item at getView() in the adapter.
Adapter:
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
Button button;
if (convertView == null) { // if it's not recycled, initialize some attributes
button = new Button(mContext);
// set layout params (make sure its GridView.layoutParams)
// and other stuff
}
else {
button = (Button) convertView;
}
button.setBackgroundResource(mThumbIds[position]); // mThumbIds hold Resource Ids
button.setOnClickListener(new OnClickListener() {
onClick(View v) {
// store directly to database here, or send it with the activity with sharedPreferences (below)
// We need an Editor object to make preference changes.
// All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences("MY_PREFERENCE", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt("button_id", mThumbIds[position]);
// Commit the edits!
editor.commit();
}
});
return button;
}
}
On Activity Side, save button onClickListener:
onClick(View v) {
// Restore preferences
SharedPreferences settings = getSharedPreferences("MY_PREFERENCE", 0);
int id = settings.getInt("button_id", -1);
// now safe all stuff to database
}
There may be details missing because a Button is focusable, but i think this should do. Also , you will achieve the selection by using a .xml defined selector resource. That, however, should be addressed in a separate question.
Edit 1:
Actually now that i think about it, i'm not sure if a drawable .xml (the selector) can have an ID. I'll have to implement this at home later on and try it.
Edit 2:
I added the sharedPreference part
Edit 3:
Added activity side querying of sharedPreference.

Categories

Resources