I have a ConstraintLayout.Group defined like this:
<android.support.constraint.Group
android:id="#+id/someGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="
textView1,
textView2,
button1" />
I change the visibility of this group from GONE to VISIBLE:
someGroup.visibility = VISIBLE
But when I try to override it by specifying the visibility of one of the views in that group:
button1.visibility = GONE
...it doesn't work. I log this visibility to logcat and it says 8 (GONE), but I can still see the view.
Any ideas what might be happening here? I tried calling requestLayout and updatePreLayout on this group, I tried changing the visibility a few times, visible, invisible and then gone. I even rebuilt the whole project because some stackoverflow answer said that it might help for visiblity issues in ConstraintLayout. I also tried versions 1.1.3 and 2.2.0-alpha. Nothing worked. It's always visible.
Update: The behavior of individual view visibility within a group has been change and is reported as fixed in ConstraintLayout version 2.0.0 beta 6. See bug fixes for ConstraintLayout 2.0.0 beta 6 .
You are seeing the correct behavior. When you place a view within a group, you give up the ability to change the individual view's visibility. I think the mechanics are that the view's visibility is set (by you) then the group's visibility is assigned (by the system) based upon group membership.
The workaround is dependent upon your needs:
Keep the view you want to control the visibility of out of a group;
Manage your own group logic and forget the groups offered by the system;
Manage group membership using setReferencedIds.
I think that this is a common complaint, but I also think that it is unlikely to be addressed. (IMHO)
I just commented on another question regarding this exact issue, so I have taken the liberty to post here (although the answer is already accepted) a simple class that will help manage a ConstraintLayout group.
ManagedGroup.java
/**
* Manage a ConstraintLayout Group view membership as a view's visibility is changed. Calling
* {#link #setVisibility(View, int)} will set a view's visibility and remove it from the group.
* Other methods here provide explicit means to manage a group's view membership.
* <p>
* Usage: In XML define
* <pre>{#code
* <[Package].ManagedGroup
* android:id="#+id/group"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:visibility="visible"
* app:constraint_referenced_ids="id1,id2,id3..." />}
* </pre>
*/
public class ManagedGroup extends Group {
private final Set<Integer> mRemovedRefIds = new HashSet<>();
public ManagedGroup(Context context) {
super(context);
}
public ManagedGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ManagedGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Set the reference ids for the group and clear the removed id array.
*
* #param ids All identifiers in the group.
*/
#Override
public void setReferencedIds(#NonNull int[] ids) {
super.setReferencedIds(ids);
mRemovedRefIds.clear();
}
/**
* Set visibility for view and remove the view's id from the group.
*
* #param view View for visibility change
* #param visibility View.VISIBLE, View.INVISIBLE or View.GONE.
*/
public void setVisibility(#NonNull View view, int visibility) {
removeReferencedIds(view.getId());
view.setVisibility(visibility);
}
/**
* Add all removed views back into the group.
*/
public void resetGroup() {
setReferencedIds(getAllReferencedIds());
}
/**
* Remove reference ids from the group. This is done automatically when
* setVisibility(View view, int visibility) is called.
*
* #param idsToRemove All the ids to remove from the group.
*/
public void removeReferencedIds(int... idsToRemove) {
for (int id : idsToRemove) {
mRemovedRefIds.add(id);
}
int[] refIds = getReferencedIds();
Set<Integer> newRefIdSet = new HashSet<>();
for (int id : refIds) {
if (!mRemovedRefIds.contains(id)) {
newRefIdSet.add(id);
}
}
super.setReferencedIds(copySetToIntArray(newRefIdSet));
}
/**
* Add reference ids to the group.
*
* #param idsToAdd Identifiers to add to the group.
*/
public void addReferencedIds(int... idsToAdd) {
for (int id : idsToAdd) {
mRemovedRefIds.remove(id);
}
super.setReferencedIds(joinArrays(getReferencedIds(), idsToAdd));
}
/**
* Return int[] of all ids in the group plus those removed.
*
* #return All current ids in group plus those removed.
*/
#NonNull
public int[] getAllReferencedIds() {
return joinArrays(getReferencedIds(), copySetToIntArray(mRemovedRefIds));
}
#NonNull
private int[] copySetToIntArray(Set<Integer> fromSet) {
int[] toArray = new int[fromSet.size()];
int i = 0;
for (int id : fromSet) {
toArray[i++] = id;
}
return toArray;
}
#NonNull
private int[] joinArrays(#NonNull int[] array1, #NonNull int[] array2) {
int[] joinedArray = new int[array1.length + array2.length];
System.arraycopy(array1, 0, joinedArray, 0, array1.length);
System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
return joinedArray;
}
}
Related
I used a RecyclerView with HORIZONTAL direction in my TV development which controlled by a D-pad to navigate the list from the left to right. the last item of the RecyclerView always lost focus when navigating to the right-most of the list.
So how can i keep the last item's focus when navigating to the end of the list?
I dug into the source code of RecyclerView, i found the onInterceptFocusSearch method in the LayoutManager, inner class of RecyclerView.
/**
* This method gives a LayoutManager an opportunity to intercept the initial focus search
* before the default behavior of {#link FocusFinder} is used. If this method returns
* null FocusFinder will attempt to find a focusable child view. If it fails
* then {#link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
* will be called to give the LayoutManager an opportunity to add new views for items
* that did not have attached views representing them. The LayoutManager should not add
* or remove views from this method.
*
* #param focused The currently focused view
* #param direction One of { #link View#FOCUS_UP}, {#link View#FOCUS_DOWN},
* {#link View#FOCUS_LEFT}, {#link View#FOCUS_RIGHT},
* {#link View#FOCUS_BACKWARD}, {#link View#FOCUS_FORWARD}
* #return A descendant view to focus or null to fall back to default behavior.
* The default implementation returns null.
*/
public View onInterceptFocusSearch(View focused, int direction) {
return null ;
}
which gives a LayoutManager an opportunity to intercept the initial focus search before the default behavior of FocusFinder is used.
So i overrided the onInterceptFocusSearch likes below, and used the CustomGridLayoutManager for my RecylerView, which works like a charming.
public class CustomGridLayoutManager extends android.support.v7.widget.GridLayoutManager {
public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super (context, attrs, defStyleAttr, defStyleRes);
}
public CustomGridLayoutManager(Context context, int spanCount) {
super (context, spanCount);
}
public CustomGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super (context, spanCount, orientation, reverseLayout);
}
#Override
public View onInterceptFocusSearch(View focused, int direction) {
int pos = getPosition(focused);
int count = getItemCount();
int orientation = getOrientation();
**********
do some logic
what i did was return the focused View when the focused view is the last item of RecyclerView.
**********
return super .onInterceptFocusSearch(focused, direction);
}
}
If you're using a subclass of BaseGridView, like HorizontalGridView or VerticalGridView, set an onKeyInterceptListener that swallows the movement key at the end of the list. For example, with a HorizontalGridView:
grid.setOnKeyInterceptListener { event ->
val focused = grid.focusedChild
event?.keyCode == KEYCODE_DPAD_RIGHT && grid.layoutManager.getPosition(focused) == grid.adapter.itemCount-1
}
If you're using RecyclerView directly, then use onInterceptFocusSearch with a custom LinearLayoutManager. For example, with a LinearLayoutManager.VERTICAL list:
list.layoutManager = object: LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
override fun onInterceptFocusSearch(focused: View?, direction: Int): View? {
if (direction == View.FOCUS_DOWN) {
val pos = getPosition(focused)
if (pos == itemCount-1)
return focused
}
if (direction == View.FOCUS_UP) {
val pos = getPosition(focused)
if (pos == 0)
return focused
}
return super.onInterceptFocusSearch(focused, direction)
}
}
inspired bythis issues ,there is another workaround:
in RecyclerView.Adapter<ViewHolder>
int focusPos;
#Override
public void onBindViewHolder(ComposeViewHolder holder,
final int position) {
....
if (focusPos == position) { // focus last clicked view again
holder.imageView.requestFocus();
}
....
holder.imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
....
focusPos = position;
notifyDataSetChanged();
}
});
}
I'm adding an answer for Kotlin.
If in each place you will override the base layout, then your code can get a lot more complicated, especially since when working with TV you will want to add something behavior to layout manager.
I checked this on Xiaomi TV stick and X62 Max with D-PAD, also with emulators, it works.
So I suggest creating a class like this:
class TvLinearLayoutManager(
context: Context?,
orientation: Int,
reverseLayout: Boolean) : LinearLayoutManager(context, orientation, reverseLayout) {
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
return if (
// This prevent focus jumping near border items
(getPosition(focused)==itemCount-1 && direction == View.FOCUS_RIGHT) ||
(getPosition(focused)==0 && direction == View.FOCUS_LEFT)
)
focused
else
super.onInterceptFocusSearch(focused, direction)
}}
So, I have a TextView like so:
<TextView
android:layout_width="fill_parent"
android:layout_height="140.7dp"
android:id="#+id/terminalOutput"
android:layout_marginBottom="0.0dp"
android:scrollbars="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:maxLines="8" />
I use it as a sort of running log, displayed to the user so they can monitor progress of a task that takes about 3 minutes. However, once I go over 8 lines, the text goes off screen. This is unintuitive to the user because they have no way of knowing that it went off screen, other than to manually poll by trying scroll down.
How can I make it so that every time I add some text to this TextView I make it scroll down as low as it can go?
Also, this is in Xamarin Android, but I don't think it's relevant. It's easy to translate between it and Java
From your Code, two steps have to do:
Step 1
Although you code in Xamarin Android, but as in Java in the xxxActivity.java for terminalOutput invoke must be like this
TextView outputText = (TextView) findViewById(R.id.terminalOutput);
outputText.setMovementMethod(new ScrollingMovementMethod());
Method setMovementMethod() by parameter ScrollingMovementMethod() is the gimmick.
Step 2
And in the layout as in Java-Android also in activity_xxx.xml for the TextView Declaration as above must have to add this
android:gravity="bottom"
When you add new line into the outputText like this:
outputText.append("\n"+"New text line.");
It will scroll to the last line automatically,
and these all the magic for your need.
As per answer here Making TextView Scrollable in Android
You don't need to use a ScrollView actually.
Just set the
android:maxLines = "AN_INTEGER"
android:scrollbars = "vertical"
properties of your TextView in your layout's xml file.
Then use:
yourTextView.setMovementMethod(new ScrollingMovementMethod());
in your code.
That will work..
None of these answers were quite what I wanted, so I came up with this.
textView.append(log);
while (textView.canScrollVertically(1)) {
textView.scrollBy(0, 10);
}
Do not forget to set movement method before scrolling
textView.setMovementMethod(new ScrollingMovementMethod());
Had the same question. Tried several decisions from this and similar discussions, nothing worked. Solved it this way:
edtConsoleText.setSelection(edtConsoleText.getText().length());
after every .append() .
You can try in 2 solutions:
Put TextView in a ScrollView
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Your Text" >
</TextView>
</ScrollView>
Use custom scroll TextView (same as traditional TextView but it can scroll)
import android.content.Context;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import android.widget.TextView;
public class ScrollTextView extends TextView {
// scrolling feature
private Scroller mSlr;
// milliseconds for a round of scrolling
private int mRndDuration = 250;
// the X offset when paused
private int mXPaused = 0;
// whether it's being paused
private boolean mPaused = true;
/*
* constructor
*/
public ScrollTextView(Context context) {
this(context, null);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/**
* begin to scroll the text from the original position
*/
public void startScroll() {
// begin from the very right side
mXPaused = -1 * getWidth();
// assume it's paused
mPaused = true;
resumeScroll();
}
/**
* resume the scroll from the pausing point
*/
public void resumeScroll() {
if (!mPaused)
return;
// Do not know why it would not scroll sometimes
// if setHorizontallyScrolling is called in constructor.
setHorizontallyScrolling(true);
// use LinearInterpolator for steady scrolling
mSlr = new Scroller(this.getContext(), new LinearInterpolator());
setScroller(mSlr);
int scrollingLen = calculateScrollingLen();
int distance = scrollingLen - (getWidth() + mXPaused);
int duration = (new Double(mRndDuration * distance * 1.00000
/ scrollingLen)).intValue();
setVisibility(VISIBLE);
mSlr.startScroll(mXPaused, 0, distance, 0, duration);
mPaused = false;
}
/**
* calculate the scrolling length of the text in pixel
*
* #return the scrolling length in pixels
*/
private int calculateScrollingLen() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
int scrollingLen = rect.width() + getWidth();
rect = null;
return scrollingLen;
}
/**
* pause scrolling the text
*/
public void pauseScroll() {
if (null == mSlr)
return;
if (mPaused)
return;
mPaused = true;
// abortAnimation sets the current X to be the final X,
// and sets isFinished to be true
// so current position shall be saved
mXPaused = mSlr.getCurrX();
mSlr.abortAnimation();
}
#Override
/*
* override the computeScroll to restart scrolling when finished so as that
* the text is scrolled forever
*/
public void computeScroll() {
super.computeScroll();
if (null == mSlr)
return;
if (mSlr.isFinished() && (!mPaused)) {
this.startScroll();
}
}
public int getRndDuration() {
return mRndDuration;
}
public void setRndDuration(int duration) {
this.mRndDuration = duration;
}
public boolean isPaused() {
return mPaused;
}
}
How to use:
ScrollTextView scrolltext = (ScrollTextView) findViewById(R.id.YourTextView);
(ScrollTextView class source: http://bear-polka.blogspot.com/2009/01/scrolltextview-scrolling-textview-for.html)
From "How to scroll to bottom in a ScrollView on activity startup":
final ScrollView scrollview = ((ScrollView) findViewById(R.id.scrollview));
scrollview.post(new Runnable() {
#Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
What you need is to put fullScroll() after append operation.
I am using Xmarin.
My solution is as many people mentioned, to textView inside a ScrollView.
If you want to see the new line at the bottom of the view, use
android:layout_gravity="bottom"
This keeps the new line at the bottom until you scroll the view. The view stays wherever you have scrolled.
No code is needed.
However, if you want the contents is always at bottom after appending, you need to add code after append():
myText.Append(...);
myscroll.FullScroll(FocusSearchDirection.Down);
For me nothing worked perfectly except a combination of these two :-
Setting scroller.scrollTo(0, 70); in your java class.Use it before setting your textview but after appending that String. 52 dp is the height of my device.You can find it out using scroller.getBottom()); So I used 70 to adjust for the scroll view.
Setting android:scrollY="30dp" in your textview.
In my case, nothing worked at first because I was apparently attempting to make changes to the UI (auto scrolling up after adding text that is out of view), outside of the UI thread. The words would display but with no auto scroll. Correction, setText would work but not append.
My call for auto scroll was not in a UI thread because I coded the call to be made in response to a socket onMessage call; which I run on its own thread. So, I had to enclose my function call with the following code and everything worked.
runOnUiThread(new Runnable() {
#Override
public void run() {
// Stuff that updates the UI
}
});
I'm sure all of the methods would work in someway so long as you are running it on a UI thread. But, to achieve the autoscroll effect for a "chat room" i.e. new text at top of chat window but when amount of text is longer than the window - autoscroll up the new messages into view, i just used the ... fullScroll(View.FOCUS_DOWN) ... method.
I am currently writing a custom ImageView. what im actually turning this imageview into is a multiple state button.
In my case, the button has three states, answered with yes, answered with no and unanswered.
I strictly don't want to use a radiogroup/button.
I have the basic setup for the imageview. It has onclick functionality to properly change the image depending on the state. Which for now is just a checkbox with a cross, an empty checkbox and a checkbox with a tick.
What I want is, when the state changes(the image is clicked) i want text, which should Always be around, and never ontop of the imageview. To change its value aswell. I want to be able to call a method similar to customCompInstance.getText(); customCompInstance.getText();
What im thinking is, the custom imageview class should have a TextView member, but I have NO clue whatsoever how to place it on the left, horizontally aligned to the imageview.
It is NOT an option to just use two different elements in xml.
Is adding a textview and placing it to the right of the imageview the right solution, if not, what do you suggest, if so, please give me some tips on how to actually achieve this.
Simplified version of my class:
public class AnswerImageView extends ImageView {
private AnswerState mSelectionState;
/**
* #param context
*/
public AnswerImageView(Context context)
{
super(context);
// TODO Auto-generated constructor stub
setImageResource(R.drawable.answer_none);
setTag(R.drawable.answer_none);
mSelectionState = AnswerState.ANSWER_NONE;
}
/**
* #param context
* #param attrs
*/
public AnswerImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
setImageResource(R.drawable.answer_none);
setTag(R.drawable.answer_none);
mSelectionState = AnswerState.ANSWER_NONE;
}
/**
* #param context
* #param attrs
* #param defStyle
*/
public AnswerImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
setImageResource(R.drawable.answer_none);
setTag(R.drawable.answer_none);
mSelectionState = AnswerState.ANSWER_NONE;
}
public AnswerState getSelectionState() {
return mSelectionState;
}
public void setSelectionState(AnswerState selectionState) {
this.mSelectionState = selectionState;
}
}
I have a method that reacts to onclicks, that all works fine. im purely asking about adding a textview in this component. Essentially combinding two components into one.
You should extend from TextView instead of ImageView and have a look at methods TextView.setCompoundDrawableXXX. Here is the link for the doc.
Is there any way to get the Drawable resource ID? For example, I am using an ImageView and I may initially use icon.png as its image but later I may change the image to icon2.png. I want to find out using the code that which image my ImageView is using from the resource. Is there any way?
This one is the best method to find out the R.drawable.img1 value when u click on the ImageView in your program. The method is something like that
in main program. First of all save the image value in the tag like that
public...activity
{
//-----this resource name is retrieved through the tag value of the drawable of (touched) ImageView //image ontouchlistener event...
ImageView imgview1.setTag("img1"); //as of R.drawable.img1
ImageView imgview2.setTag("img2"); //as of R.drawable.img2
onTouchListnener event... on imageView
{
Object tag = imageView.getTag();
int id = getResources().getIdentifier( tag, "drawable", this.getPackageName() );
switch(id)
{
case R.drawable.img1:
//do someoperation of ur choice
break;
case R.drawable.img2:
//do someoperation of ur choice
break:
}//end of switch
}//end of touch listener event
}//end of main activity
"PIR FAHIM SHAH/kpk uet mardan campus"
Are you trying to determine what the current image is on the imageview, in order to change it to some other image?
If that's so I suggest doing everything using code instead of xml.
i.e. Use setImageResource() to set the initial images during initialization and keep track of the resource ids being used somewhere in your code.
For example, you can have an array of imageviews with a corresponding array of int that contains the resource id for each imageview
Then, whenever you want to change the image, loop through the array and see what the id is.
Create a custom imageview, the rest is simple.
public class CustomImageView extends ImageView {
private int resID;
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setImageResource(int resId) {
this.resID = resId;
super.setImageResource(resId);
}
public int getResourceId() {
return resID;
}
}
There are a few steps to this:
create integer-array xml to hold names of drawables (ie: "#drawable/icon1" ... "#drawable/iconN"
use getIdentifier above to get the "array"
with ID for list of drawable, getStringArray will give you an array names of drawables you specified in step 1.
then use any of the drawable name in the array with getIdentifier again to get the drawable ID. This use "drawable" instead of "array" type.
use this ID to set image for your view.
HOpe this will help.
I now that the question is pretty old, but maybe someone will find it useful.
I have a list of TextViews with Drawables and want to set click listeners for all of them, without any need to change the code, when the layout is changed.
So I've put all drawables into a hashmap to get their ids later.
main_layout.xml
<LinearLayout android:id="#+id/list" >
<TextView android:drawableLeft="#drawable/d1" />
<TextView android:drawableLeft="#drawable/d2" />
<TextView android:drawableLeft="#drawable/d3" />
<TextView android:drawableLeft="#drawable/d4" />
<!-- ... -->
</LinearLayout>
MyActivity.java
import java.lang.reflect.Field;
import java.util.HashMap;
import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MyActivity extends Activity {
private final HashMap<ConstantState, Integer> drawables = new HashMap<ConstantState, Integer>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
for (int id : getAllResourceIDs(R.drawable.class)) {
Drawable drawable = getResources().getDrawable(id);
drawables.put(drawable.getConstantState(), id);
}
LinearLayout list = (LinearLayout)findViewById(R.id.list);
for (int i = 0; i < list.getChildCount(); i++) {
TextView textView = (TextView)list.getChildAt(i);
setListener(textView);
}
}
private void setListener(TextView textView) {
// Returns drawables for the left, top, right, and bottom borders.
Drawable[] compoundDrawables = textView.getCompoundDrawables();
Drawable left = compoundDrawables[0];
final int id = drawables.get(left.getConstantState());
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
Intent broadcast = new Intent();
broadcast.setAction("ACTION_NAME");
broadcast.putExtra("ACTION_VALUE", id);
sendBroadcast(broadcast);
}
});
}
/**
* Retrieve all IDs of the Resource-Classes
* (like <code>R.drawable.class</code>) you pass to this function.
* #param aClass : Class from R.X_X_X, like: <br>
* <ul>
* <li><code>R.drawable.class</code></li>
* <li><code>R.string.class</code></li>
* <li><code>R.array.class</code></li>
* <li>and the rest...</li>
* </ul>
* #return array of all IDs of the R.xyz.class passed to this function.
* #throws IllegalArgumentException on bad class passed.
* <br><br>
* <b>Example-Call:</b><br>
* <code>int[] allDrawableIDs = getAllResourceIDs(R.drawable.class);</code><br>
* or<br>
* <code>int[] allStringIDs = getAllResourceIDs(R.string.class);</code>
*/
private int[] getAllResourceIDs(Class<?> aClass) throws IllegalArgumentException {
/* Get all Fields from the class passed. */
Field[] IDFields = aClass.getFields();
/* int-Array capable of storing all ids. */
int[] IDs = new int[IDFields.length];
try {
/* Loop through all Fields and store id to array. */
for(int i = 0; i < IDFields.length; i++){
/* All fields within the subclasses of R
* are Integers, so we need no type-check here. */
// pass 'null' because class is static
IDs[i] = IDFields[i].getInt(null);
}
} catch (Exception e) {
/* Exception will only occur on bad class submitted. */
throw new IllegalArgumentException();
}
return IDs;
}
}
Method getAllResourceIDs I've used from here
Another approach : you just need to create your own customized view. and onCreate. then iterate the AttributeSet object(attrs) to find index of your attribute. then just call getAttributeResourceValue with index, then you will get initial ResouceID value. a simple example of extending ImageView to get ResourceID for background:
public class PhoneImageView extends ImageView {
private static final String BACKGROUND="background";
private int imageNormalResourceID;
public PhoneImageView(Context context) {
super(context);
}
public PhoneImageView(Context context, AttributeSet attrs) {
super(context, attrs);
for (int i = 0; i <attrs.getAttributeCount() ; i++) {
if(attrs.getAttributeName(i).equals(BACKGROUND)){
imageNormalResourceID =attrs.getAttributeResourceValue(i,-1);
}
}
}
public PhoneImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
This approach is suitable for who is looking to store initial values.. solution provided by Bojan Kseneman (+1 vote) is for keeping ref to resourceID whenever view is changed.
You can get the id of an image with it's name by below code.
int drawableImageId = getResources().getIdentifier(imageName,"drawable", getPackageName());
I have code that runs OnItemSelectedListener event of spinner. So when I am in the method:
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
// I want to do something here if it's a user who changed the the selected item
}
...how can I know whether the item selection was done programmatically or by a user action through the UI?
I don't know that this can be distinguished from within the method. Indeed, it is a problem that a lot of people are facing, that onItemSelected is fired when the spinner is initiated. It seems that currently, the only workaround is to use an external variable for this.
private Boolean isUserAction = false;
...
public void onItemSelected( ... ) {
if( isUserAction ) {
// code for user initiated selection
} else {
// code for programmatic selection
// also triggers on init (hence the default false)
}
// reset variable, so that it will always be true unless tampered with
isUserAction = true;
}
public void myButtonClick( ... ) {
isUserAction = false;
mySpinner.setSelectedItem ( ... );
}
You can achieve the desired result fairly simply by using Spinner's setOnTouchListener() method:
// Instance variables
boolean spinnerTouched = false;
Spinner spinner;
// onCreate() / onCreateView() / etc. method..
spinner = ...;
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
spinnerTouched = true; // User DID touched the spinner!
}
return false;
}
});
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
if (spinnerTouched) {
// Do something
}
else {
// Do something else
}
}
#Override
public void onNothingSelected(AdapterView<?> parentView) {
}
});
// Your method that you use the change the spinner selection programmatically...
private void changeSpinnerSelectionProgrammatically(int pos) {
stateSpinnerTouched = false; // User DIDN'T touch the spinner
boolean useAnimation = false;
spinner.setSelection(pos, useAnimation); // Calls onItemSelected()
}
I made a new Spinner class encapsulating the above mentioned principles. But even then you have to make sure to call the correct method and not setSelection
Same thing in a gist
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
/**
* Used this to differentiate between user selected and prorammatically selected
* Call {#link Spinner#programmaticallySetPosition} to use this feature.
* Created by vedant on 6/1/15.
*/
public class Spinner extends android.widget.Spinner implements AdapterView.OnItemSelectedListener {
OnItemSelectedListener mListener;
/**
* used to ascertain whether the user selected an item on spinner (and not programmatically)
*/
private boolean mUserActionOnSpinner = true;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (mListener != null) {
mListener.onItemSelected(parent, view, position, id, mUserActionOnSpinner);
}
// reset variable, so that it will always be true unless tampered with
mUserActionOnSpinner = true;
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (mListener != null)
mListener.onNothingSelected(parent);
}
public interface OnItemSelectedListener {
/**
* <p>Callback method to be invoked when an item in this view has been
* selected. This callback is invoked only when the newly selected
* position is different from the previously selected position or if
* there was no selected item.</p>
*
* Impelmenters can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* #param parent The AdapterView where the selection happened
* #param view The view within the AdapterView that was clicked
* #param position The position of the view in the adapter
* #param id The row id of the item that is selected
*/
void onItemSelected(AdapterView<?> parent, View view, int position, long id, boolean userSelected);
/**
* Callback method to be invoked when the selection disappears from this
* view. The selection can disappear for instance when touch is activated
* or when the adapter becomes empty.
*
* #param parent The AdapterView that now contains no selected item.
*/
void onNothingSelected(AdapterView<?> parent);
}
public void programmaticallySetPosition(int pos, boolean animate) {
mUserActionOnSpinner = false;
setSelection(pos, animate);
}
public void setOnItemSelectedListener (OnItemSelectedListener listener) {
mListener = listener;
}
public Spinner(Context context) {
super(context);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, int mode) {
super(context, mode);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
super(context, attrs, defStyle, mode);
super.setOnItemSelectedListener(this);
}
}
I have come up with a workaround that is simple and generic. Refer the accepted answer to this question:
Undesired onItemSelected calls
So, if position is not equal to spin.getTag(R.id.pos), you know that the callback was due to the user making a change, because whenever you yourself are making the change, you are setting the tag as spin.setTag(R.id.pos, pos) where pos is the value you set. If you are using this approach, make sure to set the tag in onItemSelected after you finish your work!
Unlike a SeekBar a Spinner does not have built-in support for detecting whether the change was programmatic or by the user, so I suggest that, never use a spinner for any kind of recursive programmatic tasks. I had very bad experience when I tried to implement a MediaPlayer with recursive connection to a SeekBar and a Spinner. The result was full of disappointment. So you can attempt only if you like unhappiness and disappointment.
Note:
I solved my issue by adding an apply Button to my spinner selection.
Don't waste our time for solving unnecessary things. I mean doing a work around is not a good practice rather re-implement the Spinner to have an our own expected behavior.
I am really sorry if the above statements are wrong. I shared this because I love coders and coding.
I know this is late,but I just started on android and faced this issue and I found a suitable work around for it.
I used a workaround based on the focusable in touch mode scenario.
Set the spinner view as focusable in touch mode.
Set an on focus change listener of the spinner to call the spinner.performClick() when focused.
in the onItemSelected listener of the spinner,return the focus to the parent view of the layout(or which ever view you find suitable)
User input can be identified by checking if the spinner has focus as programmatic changes will not requestfocus.
PS: when you set the focusableintouchmode for the spinner in onCreate, make sure you return the focus to the parent view immediately in case you lack any other focusable views.
I made a mashup of #ban-geoengineering's and #vedant's.
Available as a gist at https://gist.github.com/paulpv/9f6f1cd81945a3029ee3343a3543fe1c
I do not like the accepted isUserAction answer.
#ban-geoengineering's answer is cute for one Spinner, but not when you have more than one.
I did not like needing to have a special changeSpinnerSelectionProgrammatically method in #vedant's answer.
I see this as a reasonable best of both worlds:
package com.prometheanworld.audiotest
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.widget.AdapterView
import androidx.appcompat.widget.AppCompatSpinner
/**
* A subclass of AppCompatSpinner that adds `userTouched` detection
*/
#SuppressLint("ClickableViewAccessibility")
class MySpinner : AppCompatSpinner {
companion object {
private const val MODE_THEME = -1
}
/**
* A clone of AdapterView.OnItemSelectedListener that adds a `userTouched: Boolean` parameter to each method.
*/
interface OnItemSelectedListener {
/**
*
* Callback method to be invoked when an item in this view has been
* selected. This callback is invoked only when the newly selected
* position is different from the previously selected position or if
* there was no selected item.
*
* Implementers can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* #param parent The AdapterView where the selection happened
* #param view The view within the AdapterView that was clicked
* #param position The position of the view in the adapter
* #param id The row id of the item that is selected
* #param userTouched true if the user touched the view, otherwise false
*/
fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long, userTouched: Boolean)
/**
* Callback method to be invoked when the selection disappears from this
* view. The selection can disappear for instance when touch is activated
* or when the adapter becomes empty.
*
* #param parent The AdapterView that now contains no selected item.
* #param userTouched true if the user touched the view, otherwise false
*/
fun onNothingSelected(parent: AdapterView<*>?, userTouched: Boolean)
}
private var userTouched = false
private var externalOnTouchListener: OnTouchListener? = null
private var internalOnTouchListener = OnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> userTouched = true
}
externalOnTouchListener?.onTouch(v, event) ?: false
}
private var externalOnItemSelectedListener: OnItemSelectedListener? = null
private val internalOnItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
externalOnItemSelectedListener?.onItemSelected(parent, view, position, id, userTouched)
userTouched = false
}
override fun onNothingSelected(parent: AdapterView<*>?) {
externalOnItemSelectedListener?.onNothingSelected(parent, userTouched)
userTouched = false
}
}
constructor(context: Context) : this(context, null)
#Suppress("unused")
constructor(context: Context, mode: Int) : this(context, null, R.attr.spinnerStyle, mode)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.spinnerStyle)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : this(context, attrs, defStyle, MODE_THEME)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int, mode: Int) : this(context, attrs, defStyle, mode, null)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int, mode: Int, popupTheme: Resources.Theme?) :
super(context, attrs, defStyle, mode, popupTheme) {
super.setOnTouchListener(internalOnTouchListener)
super.setOnItemSelectedListener(internalOnItemSelectedListener)
}
override fun setOnTouchListener(listener: OnTouchListener?) {
externalOnTouchListener = listener
}
override fun setOnItemSelectedListener(listener: AdapterView.OnItemSelectedListener?) {
throw UnsupportedOperationException("Use setOnItemSelectedListener(listener: MySpinner.OnItemSelectedListener?) instead")
}
fun setOnItemSelectedListener(listener: OnItemSelectedListener?) {
externalOnItemSelectedListener = listener
}
}