This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Android Spinner OnItemSelected Called Erroneously (without user action on opening spinner)
Does anyone know how to prevent the onItemSelected() (OnItemSelectedListener interface) method from running when the layout is instantiated? I need to know if there is a way to do this because I want to keep how I instantiate my layout separate from this listener.
I have tried creating an if statement initially set to false around all the code inside of the overridden method, but there is no way of knowing when to set it to true because the overridden method runs after the onCreate(), onStart(), and onResume() methods everytime.
I have not found any clear cut answers on this. Any clear cut solutions would be greatly appreciated.
David, here is a tutorial I wrote up for this problem...
Problem Statement
an undesirable onItemSelected() is triggered whilst the Gallery (or Spinner) is initializing.
This means that code is prematurely executed; code which is intended to execute ONLY when a user physically makes a selection.
Solution
in onCreate(), count how many Gallery (or Spinner) widgets you have in the view. (mGalleryCount)
in onItemSelected(), count how often it has triggered. (mGalleryInitializedCount)
when (mGalleryInitializedCount < mGalleryCount) == false, then execute the code meant for the user
Code Example
public class myActivity extends Activity implements OnItemSelectedListener
{
//this counts how many Gallery's are on the UI
private int mGalleryCount=0;
//this counts how many Gallery's have been initialized
private int mGalleryInitializedCount=0;
//UI reference
private Gallery mGallery;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.myxmllayout);
//get references to UI components
mGallery = (Gallery) findViewById(R.id.mygallery);
//trap selection events from gallery
mGallery.setOnItemSelectedListener(this);
//trap only selection when no flinging is taking place
mGallery.setCallbackDuringFling(false);
//
//do other stuff like load images, setAdapter(), etc
//
//define how many Gallery's are in this view
//note: this could be counted dynamically if you are programmatically creating the view
mGalleryCount=1;
}
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if (mGalleryInitializedCount < mGalleryCount)
{
mGalleryInitializedCount++;
}
else
{
//only detect selection events that are not done whilst initializing
Log.i(TAG, "selected item position = " + String.valueOf(position) );
}
}
}
Why this works
this solution works because the Gallery finishes initialization long before a user is physically able to make a selection.
Here is a modified version of "Someone Somewhere" code. You can use it if you have a single view.
public class myActivity extends Activity implements OnItemSelectedListener
{
// Set view initialization to false while the it is being built
private boolean initializedView = false;
//UI reference
private Gallery mGallery;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.myxmllayout);
//get references to UI components
mGallery = (Gallery) findViewById(R.id.mygallery);
//trap selection events from gallery
mGallery.setOnItemSelectedListener(this);
//trap only selection when no flinging is taking place
mGallery.setCallbackDuringFling(false);
//
//do other stuff like load images, setAdapter(), etc
//
}
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if (initializedView == false)
{
initializedView = true;
}
else
{
//only detect selection events that are not done whilst initializing
Log.i(TAG, "selected item position = " + String.valueOf(position) );
}
}
}
Same solution:
private int m_intSpinnerInitiCount = 0;
private static final int NO_OF_EVENTS = 1;
...
m_spnTemplates.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView,
View selectedItemView, int position, long id) {
//trying to avoid undesired spinner selection changed event, a known problem
if (m_intSpinnerInitiCount < NO_OF_EVENTS) {
m_intSpinnerInitiCount++;
} else {
//YOUR CODE HERE
}
}
I ran into this problem yesterday with an OnCheckedChangedListener. I ended up adding a boolean instance variable initialized to true inside of my adapter class with an accessor method isListenerEnabled(). I then set the variable to false in my layout code and set it to true again at the end of the layout code. In my listener, I inspect the value of the variable to decide whether to execute the listener code or not.
You can set your variable back to false everytime on onPause is called.
As for when to set it to true, you could do this for the first motion/key/trackball event after onResume.
I also looked for a good solution on the internet but didn't find any that satisfied my needs.
So I've written this extension on the Spinner class so you can set a simple OnItemClickListener, which has the same behaviour as a ListView.
Only when an item gets 'selected', the onItemClickListener is called.
Have fun with it!
public class MySpinner extends Spinner
{
private OnItemClickListener onItemClickListener;
public MySpinner(Context context)
{
super(context);
}
public MySpinner(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public MySpinner(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener inOnItemClickListener)
{
this.onItemClickListener = inOnItemClickListener;
}
#Override
public void onClick(DialogInterface dialog, int which)
{
super.onClick(dialog, which);
if (this.onItemClickListener != null)
{
this.onItemClickListener.onItemClick(this, this.getSelectedView(), which, this.getSelectedItemId());
}
}
}
Related
I got 2 recyclerviews on a fragment. Both recyclerviews contain items and checkboxes. Only a single checkbox should be selectable within the two recyclerviews.
So if a checkbox is selected, all other checkboxes should be switched off IN BOTH RECYCLERVIEWS etc..
Here is my current code.
this code means that only one checkbox can currently be selected in each recyclerview.
My two recylcerviewadapters look like this (NOTE: both a quiet identical so I'm only posting one of them):
#Override
public void onBindViewHolder(#NonNull NameViewHolder holder, #SuppressLint("RecyclerView") int position) {
//getting broadcast from 1st recyclerviewadapter
LocalBroadcastManager.getInstance(context).registerReceiver(mNameReceiver, new IntentFilter("checkbox_first_adapter"));
String name = namesArrayList.get(position);
holder.nameView.setText(name);
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
rowIndex = position;
notifyDataSetChanged();
//If checkbox is checked, send broadcast to 2nd recyclerviewadapter
sendCheckBoxBroadCast();
isClicked = true;
}
});
if (isCheckBoxChecked) {
holder.checkBox.setChecked(false);
}
if (!isClicked) {
if (selectedName != null) {
if (name.equals(selectedName.getName())) {
holder.checkBox.setChecked(true);
sendCheckBoxBroadCast();
}
}
} else {
if (rowIndex == position) {
holder.checkBox.setChecked(true);
} else {
holder.checkBox.setChecked(false);
}
}
}
public BroadcastReceiver mNameReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
isCheckBoxChecked = intent.getBooleanExtra("isCheckBoxChecked", false);
}
};
private void sendCheckBoxBroadCast() {
Intent intent = new Intent("checkbox_second_adapter");
intent.putExtra("isCheckBoxChecked", true);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
How can I signal the 2nd recyclerview adapter that a checkbox has been selected so that it knows to unselect all checkboxes?
You can go three ways with this:
The first, dirty way is to just pass one adapter to the other and viceversa, and when one has a checkbox event, it callbacks the other one.
The second way would be to implement some sort of interface. You said that both adapters are similar, so it shouldn't be a problem. This interface would have a single method, the callback to update the checkboxes. From the caller activity class, you should be able to manage them both (you could also be able to put them in an array, if you ever need to add more) and get callbacks from either one of the adapters.
The third and perhaps the cleanest way would be to merge the recylcerviews: since a recyclerview can display different kind of views (you just have to fiddle a bit with the onBindViewHolder method) it could be possible (depends on the situation, you haven't provided enough information for this to be possible or entirely ruled out) to use only one adapter class.
This question already has answers here:
Android : How to set onClick event for Button in List item of ListView
(10 answers)
Closed 4 years ago.
I'm making an inventory tracker app for Android for a practice project. I use SQLite for the storage and my ListView displays the contents of the database using a CursorAdapter. I use CursorLoader to fetch the data from the database
Each row in my ListView has a couple of TextViews and a Button. I plan to use the Button to decrement the quantity column/property of the selected item in the database.
Where do I setup the button OnClick listener? In my Activity class or my CursorAdapter class' bindView()?
Also how can I detect which row the button was pressed on from the button click?
I've already used the ListView's onItemClickListener to send the user to a detailed Activity that display more info about the current row. That had an id argument that gets passed. So I'm finding an equivalent that I can use for the buttons I put on each row.
Look the following. I used the Custom adapter to add the onItemClickListener:
private class MyListAdapter(val ctx: Context, val values: List<MyListInformatin>) : ArrayAdapter<MyListInformatin>(ctx, -1, values) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rowView = inflater.inflate(R.layout.item_my_layout, parent, false)
rowView.setOnClickListener {
val detailsActivity = Intent(ctx, DetailsActivity::class.java)
detailsActivity.putExtra(DetailsActivity.ROUTE_NUMBER_PARAM, values.get(position).routeNumber)
val bundle = Bundle()
bundle.putParcelable(detailsActivity.ROUTE_NO_STRUCTURE_PARAM, values.get(position).strucutre)
detailsActivity.putExtra(detailsActivity.PARAM_BUNDLE, bundle)
ctx.startActivity(detailsActivity)
}
return rowView
}
}
You can create an interface inside your adapter and a declare a fucntion in this interfae like ** void itemClick(int position)** and implement that interface in your activity
for void itemClick(int position) method :- Define it on
holder.setOnClickListener() block like -
viewHolder.button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (yourInterface != null) {
yourInterface.itemClick(position);
}
}
});
For detailed description refer the below link -
https://www.c-sharpcorner.com/UploadFile/9e8439/create-custom-listener-on-button-in-listitem-listview-in-a/
This question has been answered already, you need to setup your list items as not clickable and manually listen for onClick events of buttons, see a detailed answer here: Android : How to set onClick event for Button in List item of ListView
public class MyCustomAdapter extend Adapter {
private MyOnClickListener mListener;
#Override
public View getView(int position, View view, ViewGroup parent)
{
...
// Your view
mContainer.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
mListener.onWholeItemClick(position);
}
});
// Your button
mButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
mListener.onButtonClick(position);
}
});
}
public void setListener(MyOnClickListener listener) {
this.mListener = listener;
}
// Listener interface
public interface MyOnClickListener() {
void onWholeItemClick(int position); // Can pass data as a parametter
void onButtonClick(int position);
}
}
public class MyActivity extend Activity implement MyOnClickListener() {
...
#Override
public void onCreate(...) {
mAdapter = new MyCustomAdapter(this);
mAdapter.setListener(this); // Pass the listener to adapter
}
#Override
public void onWholeItemClick(int position) {
//
}
#Override
public void onButtonClick(int position) {
//
}
}
That how you can fully hanhle click events
In onBindViewHolder give a tag to that button. that tag can be the position of item or the item itself . ( tag can be object )
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
CUSTOMMODEL item = items[position];
...
holder.button.setTag(item);
//OR
holder.button.setTag(position);
...
holder.button.setOnClickListener(clickListener);
}
in your onClick(View view) method, get tag from the view and you can understand which item is clicked.
In my android app where I am creating a tic tac toe game, I have this code below where if it's player one move then set their selection as X with a particular colour, else it must be player 2 so set text as O for their selection with a different colour.
#Override
public void onClick(View v) {
if (!((Button) v).getText().toString().equals("")) {
return;
}
if (playerOneMove) {
((Button) v).setText("X");
((Button) v).setTextColor(Color.parseColor("#e8e5e5"));
} else {
((Button) v).setText("O");
((Button) v).setTextColor(Color.parseColor("#737374"));
}
...
}
I have a problem though and it is in regards to when I rotate the screen. When I rotate the screen, the text for X and O both change to the default text colour android studio provides. I want to keep the colours for these text but I am not sure how to do that? I have made sure there is no global text colour set.
Below is my code that handles orientation changes:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("playerOneMove", playerOneMove);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
playerOneMove = savedInstanceState.getBoolean("playerOneMove");
}
You need to save the colours of each box of your tic tac toe board and retain them again on your layout configuration changes (i.e. device rotation).
You might consider looking into the answer here for a detailed explanation of your problem. You might check the developer documentation here for handling the configuration changes as well.
The key idea is to save the layout statues in variables which survives the configuration changes and update them accordingly in your onCreate or onCreateView function in case of Activity and Fragment respectively. However, in your case, you need to store a lot of data and on each configuration change, you need to restore them again which is not an efficient way to do that. I would like to recommend you look for other available options which survive the orientation or configuration changes of your layout.
I would strongly suggest implementing ViewModel in your case, which survives the application configuration change and handles the overall UI representation in the most effective way. The idea is to bind your UI elements with your ViewModel and then retain the UI elements each time from your ViewModel. It can be retained at the exact state until the Activity or Fragment finishes.
In your case, I would like to provide an example of how you can prepare a ViewModel. Let us consider your ViewModel is GameModel which saves the layout items of your board.
public class GameModel extends ViewModel {
public final LiveData<Game> gameLiveData = new LiveData<>();
public GameModel() {
// trigger game load.
}
void doAction() {
// depending on the action, do necessary business logic calls and update the gameLiveData.
}
}
public class Game {
public static final int CROSS = 1;
public static final int ZERO = 0;
public int pos1 = -1; // Default values are -1, when the position is yet to be played
public int pos2 = -1;
public int pos3 = -1;
public int pos4 = -1;
public int pos5 = -1;
public int pos6 = -1;
public int pos7 = -1;
public int pos8 = -1;
public int pos9 = -1;
}
Now from your Activity, you need to add an observer to your GameModel class to update the UI accordingly.
public class UserActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_activity_layout);
final GameModel viewModel = ViewModelProviders.of(this).get(GameModel.class);
viewModel.gameLiveData.observer(this, new Observer() {
#Override
public void onChanged(#Nullable Game gameData) {
// update ui.
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Pass parameters in doAction function to set the items in the Game class and update the ui accordingly inside the onChanged function.
viewModel.doAction();
}
});
}
}
Hope that helps!
I have ImageViews inside of a GridView, I had been using an OnItemClickListener along with an OnItemLongClickListener set on the GridView to open the image on a larger page and to delete the item respectively. Now, I have to implement rearranging of the ImageViews in the GridView, so I plan to move the deletion function to a double tap gesture, (please do not lecture me on android style guidelines (including the possibility of contextual actionbars, which I suggested), as this is what my boss asks for to emulate functions inside our ios app) in order to reserve long click for the drag and drop. I set an OnTouchListener on each view in the getView of my custom adapter, feeding a GestureDetecter with a listener extending SimpleOnGestureListener the given MotionEvent with onTouchEvent. I know what to do up to that point, but when I included (onDown of course, to get other callbacks) onDoubleTap, onSingleTapConfirmed, and onLongPressed all taps were interpreted as long clicks. And when I removed the both callback methods to be replaced with their listener counterparts once again (ie OnItemClickListeners) I received those two gestures but not the double tap, which makes sense, as double taps start out as a single tap unless you wait for a bit less than a second to confirm them as singles rather than potential doubles. I also tried placing the OnItemClickListener, but not the OnItemLongClickListener, with the callback in the extended SimpleOnGestureListener. In this case, only long presses were ever interpreted, but other gestures caused no response. Here is my code as it stands now, and do note that I returned false in the onTouchEvent in order to allow others (itemclicklisteners) to consume the events following the attempts made in the GestureDetector.
public class MainBoardGridAdapter extends GenericBoardGridAdapter implements OnItemLongClickListener {
private class Ges extends GestureDetector.SimpleOnGestureListener {
int pos;
public Ges(View v) {
pos = (Integer) v.getTag();
}
#Override
public boolean onDown(MotionEvent me) {
//this does get called but none of these methods below
return true;
}
#Override
public boolean onDoubleTap(MotionEvent me) {
new DeleteConfirmationPrompt(c, "board") {
#Override
protected boolean onDeleteConfirmed() {
// delete the visionboard
return deleteBoard(pos);
}
}; // Constructor shows dialog
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
MainBoardGridAdapter.super.flagForUpdate(pos);
if (listener != null) {
listener.onBoardClick(pos, getName(pos));
} else {
Intent intent = new Intent(c, VisionBoardActivity.class);
intent.putExtra(VisionBoardActivity.EXTRA_VISION_BOARD_NAME, getName(pos));
frag.startActivityForResult(intent, MyBoardsFragment.REQUEST_EDIT);
}
return false;
}
}
#Override
public boolean onItemLongClick(AdapterView<?> parent, View v,
final int pos, long id) {
Toast.makeText(c, "Long", Toast.LENGTH_LONG).show();
return false;
}
// called by getView of extended adapter
#Override
public void onImageLoaded(ImageView iv, String data, View root) {
iv.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
(new GestureDetector(c, (new Ges(v)))).onTouchEvent(event);
return false;
}
});
}
}
And in the Activity, gv is my GridView:
gv.setOnItemLongClickListener(gridAdapter);
Also note that I had been using true in the return value in the GestureDetector methods, until trying the current configuration.There was no difference to be seen.
Thank you for your valuable time and help, I hope that someone will be able to point out what I am doing incorrectly.
-Jackson
I have a Listview that can only select one item. When that item gets clicks, it runs an AsyncTask. In the onPostExecute(), an AlertBox Dialog pops up. But what I'm trying to do is get the selected item to display inside the alertBox and I've tried everything I could think of. Any help would be appreciated, and thank you in advance.
Here is my ListView setup.
Public class MyClass extends Activity
{
list.setAdapter(new ArrayAdapter<String>(this, R.layout.vacation_tracks, vacation_menu));
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
for(int i = 0; i<vacation_menu.length; i++)
{
if(((TextView) view).getText().equals(vacation_menu[i]))
{
Sizes work = new Sizes();
work.execute(tempLink);
}
}
});
}
And this is my AsyncTask class. My goal is to get the selected item (or text from TextView associated with selected item) in the Title() method in the onPostExecute().
Private Class Sizes extends AsyncTask<URL, Void, Float>
{
protected float doInBackground(URL...urls)
{
//gets url.getContentLength();
}
protected void onPostExecute(Float result)
{
AlertDialog.Builder alertbox = new AlertDialog.Builder(Vacation.this);
alertbox.setMessage( Title( ITEM FROM LISTVIEW ) );
alertbox.setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface arg0, int arg1)
{
}
});
alertbox.setNegativeButton("No", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface arg0, int arg1)
{
}
});
alertbox.show();
}
}
Thank you again for any help!
If your Task is defined within the scope of your Activity, you can use the final keyword:
final String alertBoxTitle = vacation_menu[i];
Sizes work = new Sizes();
work.execute(tempLink);
and
alertbox.setMessage(alertBoxTitle);
If your Task is not within the scope of your Activity you could pass the title as an argument or via a setter. Setter seems easier in your case.
Within your Task:
String title;
public void setTitle(String title) {
this.title = title;
}
protected void onPostExecute(Float result) {
AlertDialog.Builder alertbox = new AlertDialog.Builder(Vacation.this);
alertbox.setMessage(title);
// ...
}
Use it like this:
Sizes work = new Sizes();
work.setTitle(vacation_menu[i]);
work.execute(tempLink);
You could use the position argument of your onItemClick listener to fetch the clicked item from your data source, then pass this data to the AsyncTask object, and consume it there (display it in the Alert box)
If the AsynTask is inner to your activity then you can access the listview member and get the selected item. Call
mListView.getSelectedItem(); // returns the object associated with this item.
Or
You can pass the object to the AsyncTask through the parameters.
Pass the title string to the Size constructor. Like this
Sizes work = new Sizes(mListView.getSelectedItem().getTitle());
work.execute(tempLink);
if you just want to create an alert dialog you don't need the AsyncTask..
Just add the code getSelectedItem in your onListItemClick and create an alert from that..