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());
Related
I have used android.support.design.widget.TextInputLayout to make a password input that allows the user to toggle readability on the password. The xml is as follows:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="#drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
The drawable selector is as described by How to customize android passwordToggleDrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="#drawable/password_toggle_hide"/>
</selector>
The issue is that the custom drawable becomes really large. Not larger than the edittext, but rather it seems to maximize its size while still fitting inside it (so, it seems to be bounded by the height of the element). However, if I leave the passwordToggleDrawable property unset, the drawable for the toggle is sized as is normal for android (I am sure you have seen the icon in other apps before). After much searching I have found a way to resize the custom one, but I am not happy with how its done (requires 2 extra xml files per drawable) and it only works for API 23+.
I would like to know if there is a good way to set the size of the drawable, or better yet, make it target the size of the default drawable?
I have tried setting the padding of the EditText as the source of TextInputLayout says that it gets the four paddings from it and apply to the mPasswordToggleView (line 1143), but it made no change on the icon and (as expected) also affected the padding of the EditText. I have tried setting minheight to 0. I have also tried changing between EditText and TextInputEditText (using the latter now as it seems to be recommended). I have tried switching the layout_height properties to wrap_content. I have tried scaling the drawable using xml's <scale> tag with the scale properties set. I have tried similarly with the <inset> tag. But none of those methods works.
The way I found (and am currently using) to resize the drawable that actually works is by using the xml tag <layer-list>, while setting the width and height properties. Then the <selector> xml file references those resized drawables instead of the png ones. But I don't like this solution because as I mentioned it requires API 23 and because of that results in a total of 4 extra xml files. It also sets the width and height by themselves, instead of keeping the ratio locked.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR
How do I set the size of a custom passwordToggleDrawable in TextInputLayout? Preferably to same size as the default drawable.
I know this is an old question, but I faced the same problem and I believe I figure out a simple solution for this.
I'm using the TextInputLayout for the newest material library, and the only thing that I did was to find the reference for the endIcon from the TextInputLayout and change it's minimum dimensions.
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
Important things to notice:
I did this on the OnFinishedInflated from a custom TextInputLayout, but I believe it will work fine on some activity class.
Cheers!
I face same problem. To avoid this situation I used png and set them based dpi like drawable-hdpi, drawable-mdpi etc. Also make those drawable as per radio. Hope that this tricks also work for you.
I were unable to find any solution to the question I actually asked, but I decided to instead solve the issue by disregarding the "in InputTextLayout" part of the question and implemented my own version of the class.
Mostly it is just a copy of InputTextLayout (sadly that class doesnt translate well for subclassing as everything is private) but with most of the stuff I dont need removed, and more importantly, with the CheckableImageButton mPasswordToggleView changed to a ViewGroup containing a View.
The ViewGroup is the clickable button, and handles setMinimumDimensions to keep the clickable area at min 48 dp, like the original did through design_text_input_password_icon.xml. This also makes small drawables not hug the right side of the screen as they are centered in the clickable area, giving the margin that the default drawable appears to have.
The View (or more precisely, a new subclass of it I called CheckableView) is the actual drawable (setBackground()), replacing the CheckableImageButton as the container of the drawable that lets it switch based on state_checked selector.
The xml-property passwordToggleSize allows a dimension to be set, which is used to scale the drawable. I opted to only have one value instead of width&height, and the drawable scales with its ratio locked such that its greatest dimension matches the dimension specified. I made the default size 24dp, as is specified for the default-drawable in design_ic_visibility.xml.
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
#Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
#Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
And then in an attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
Same issue for me. The problem comes from the gradle material API implementation:
implementation 'com.google.android.material:material:1.1.0'
downgrade to version 1.0.0 fixes the issue:
implementation 'com.google.android.material:material:1.0.0'
My requirements are to display a vertical list of views with a scrollbar whenever the total height of the views is bigger than the allowed height for the list.
Additionally, I need to customize the scrollBar appearance (background and thumb) programmatically. (At runtime, my activity will receive all the data to do the rendering : the bitmap to use as scrollbar background, the scrollbar width and the bitmap to use as scrollbar's thumb.)
A ListView seems a good candidate for this, except that I can't find a way to customize the scrollBar programmatically.
I read this question scrollBar in a listView...customizing it. but, it's using a theme, and AFAIK it's not possible the create a new theme programmatically.
So my question(s):
Is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
[only if the answer to the first one is "definitively not possible"] do you see any other way to achieve those requirements (and some working example of doing it)
Thanks.
EDIT (23/12)
I found this hidden class android.widget.ScrollBarDrawable. May be a good starting point to build a solution (no time to investigate it right now).
is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
Literally, it does not look like it, simply because there are no setters for manipulating it.
It is conceivable that you could create your own BenView subclass of View that replaces the hidden ScrollBarDrawable with BenScrollBarDrawable that handles dynamic changes. But then you would need to create BenViewGroup, BenAbsListView, and BenListView, cloning their Ben-less counterparts' source code, to have them chain up to BenView to inherit this behavior. And, since one of ViewGroup, AbsListView, or ListView is bound to change in any Android release, you will actually need N copies of these classes and choose the right copy based on API level at runtime. And then your app may still behave oddly on some devices where the manufacturer went in and tinkered with ViewGroup, AbsListView, or ListView, and you will not be using their code.
I doubt that this is worth it, particularly since scrollbars are not visible by default except while scrolling.
do you see any other way to achieve those requirements (and some working example of doing it)
Handle your own touch events to do your own scrolling and do your own scrollbar stuff. Whether this is less work than the above solution is up for debate.
I just faced the same problem for RecyclerView and solved it like this
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
/**
* Created on 22.3.2016.
*
* #author Bojan Kseneman
* #description A recycler view that will draw the scroll bar with a different color
*/
public class CustomScrollBarRecyclerView extends RecyclerView {
private int scrollBarColor = Color.RED;
public CustomScrollBarRecyclerView(Context context) {
super(context);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScrollBarColor(#ColorInt int scrollBarColor) {
this.scrollBarColor = scrollBarColor;
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
}
These methods are defined in View class, so the same princible should work of other views like ScrollView and ListView.
Just change the android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb"
and create a drawable as per your need.
Like
<ListView android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb" />
in the Drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient android:angle="0" android:endColor="#6699FF" android:startColor="#3333FF" />
<corners android:radius="1dp" />
<size android:width="1dp" />
</shape>
I think this is what you want!
If you only need to support API 24 or later, you can use a custom drawable to accomplish this task. You'll need to decide how you're going to allow the drawable to communicate with the activity. Here is one possible solution.
res/drawable/my_custom_scrollbar_thumb.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_thumb" />
res/drawable/my_custom_scrollbar_track.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_track" />
somewhere in your layout file
<ListView
android:scrollbarTrackVertical="#drawable/my_custom_scrollbar_track"
android:scrollbarThumbVertical="#drawable/my_custom_scrollbar_thumb"
the drawable implementation
public class RegisteredMutableDrawable extends DrawableWrapper
{
public static interface Registrar
{
public void register(int _id, RegisteredMutableDrawable _drawable);
}
public static Registrar registrar;
private static int[] ATTRS = new int[] { android.R.attr.id };
public RegisteredMutableDrawable()
{
super(null);
}
public void inflate(Resources _res, XmlPullParser _parser, AttributeSet _as, Theme _theme)
throws XmlPullParserException, IOException
{
super.inflate(_res, _parser, _as, _theme);
final Registrar r = registrar;
if (r != null)
{
final TypedArray ta = _res.obtainAttributes(_as, ATTRS);
final int id = ta.getResourceId(0, 0);
ta.recycle();
r.register(id, this);
}
}
}
in your activity
public class MyActivity extends Activity implements RegisteredMutableDrawable.Registrar
{
#Override public void onCreate(Bundle _icicle)
{
super.onCreate(_icicle);
RegisteredMutableDrawable.registrar = this;
setContentView(R.layout.whatever);
RegisteredMutableDrawable.registrar = null; // don't leak our activity!
}
#Override public void register(int _id, RegisteredMutableDrawable _drawable)
{
switch(_id)
{
case R.id.scroll_thumb:
_drawable.setDrawable(myThumbDrawable);
break;
case R.id.scroll_track:
_drawable.setDrawable(myTrackDrawable);
break;
}
}
}
You can use the jquery plugin(jquery.nicescroll.js) to achieve this. More details visit
http://areaaperta.com/nicescroll/
I am working on an Android application where I deliver some built-in images and give the user the option to download some more from the web to use in the application. At some point in my app, I look at an ImageView in my layout and want to determine if the Drawable inside is an in-built resource or an image I have downloaded from the web to the SD Card.
Is there any way to extract the URI of the Drawable used in the ImageView? This way I would be able to see if it is a resource or a downloaded file.
Here is my code so far:
ImageView view = (ImageView) layout.findViewById(R.id.content_img);
Drawable image = view.getDrawable();
UPDATE:
Using Barry Fruitman's suggestion from below, I stored the URI of the image directly inside my custom ImageView for later use. Here is what my implementation looks like:
public class MemoryImageView extends ImageView {
private String storedUri = null;
public MemoryImageView(Context context, String Uri) {
super(context);
}
public MemoryImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MemoryImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public String getStoredUri() {
return storedUri;
}
public void setStoredUri(String storedUri) {
this.storedUri = storedUri;
}
}
And usage looks like this:
MemoryImageView view = (MemoryImageView) layout.findViewById(R.id.content_img);
String img = view.getStoredUri();
if(img.startsWith("android.resource")) {
//in-built resource
} else {
//downloaded image
}
No. Once you create the Drawable that information is lost. What I suggest you do is subclass the ImageView and add extra member(s) to keep track of whatever you want.
Replace:
<ImageView />
with
<com.mypackage.MyImageView />
And create:
class MyImageView extends ImageView {
protected final int LOCAL_IMAGE = 1;
protected final int REMOTE_IMAGE = 2;
protected int imageType;
}
MyImageView will behave exactly like an ImageView, but with that extra member that you can read and write anywhere you want. You will probably also have to override the ImageView contructor(s) with constructors that just call super().
I have set an image for an ImageView using the setImageResource(R.drawable.icon1).
Now my requirement is to find out what is the image that is set for an ImageView and do some processing.
Like
if (imageView.getImageResource() == R.drawable.icon1) {
//do some processing
else if (imageView.getImageResource() == R.drawable.icon2) {
//do somethign else
else
//display
So I would like to know if there exists a method(AFAIK, it doesn't) and if it doesn't how to keep a track of what resources have been set for an ImageView.
Thanks.
Sana.
You're assuming that because you put an integer in, you are able to get an integer back out, and that's not how setImageResource() works. See ImageView#setImageResource(). This is just a convenience method for you: what Android is doing behind the scenes, is looking up the Drawable resource (in most cases, it's a BitmapDrawable, but it could be any type), and then applying that resource to the ImageView as a Bitmap object (i.e., image data only -- it does not have any idea what its original "resource id" was previously).
Your best solution is going to be keeping track of the last resource id you used:
imageView.setImageResource(R.drawable.image1);
this.mLastResourceId = R.drawable.image1;
// ...
// Later, when checking the resource:
if (this.mLastResourceId == R.drawable.image1) {
// Do something
}
You should be able to use the tag property as follows:
imageView.setImageResource(R.drawable.icon1);
imageView.setTag(Integer.valueOf(R.drawable.icon1));
// later
int drawable = (Integer) imageView.getTag();
switch(drawable) {
case R.drawable.icon1:
// do_something....
break;
case R.drawable.icon2:
// do_something....
break;
...
}
Another alternative, if possible, would be to subclass the ImageView and store the integer in an overridden setImageResource().
public class MyImageView extends ImageView {
int rememberId = -1;
#override void setImageResource(int resId){
rememberId = resId;
}
int getMyResId(){
return rememberId;
}
}
You should be able to use Bundle and set whatever properties you need.
Intent i = new Intent();
Bundle extras = new Bundle();
i.putExtra("prop", "value");
package com.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageButton;
/**
* custom star type imagebutton that can be obtain the background image resource
* id.
*
* </p> your layout xml resource might be like the following code: <br>
* <view class="com.widget.StarButton" <br>
* android:id="#+id/starButton" <br>
* android:layout_width="wrap_content" <br>
* android:layout_height="wrap_content" <br>
* android:background="#android:color/background_light" <br>
* android:paddingTop="10dp" <br>
* android:src="#drawable/star" /> <br>
*
* #author Jeffen
*
*/
public class StarButton extends ImageButton {
private int mLastResourceId = -1;
public StarButton(Context context) {
super(context);
}
public StarButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.imageButtonStyle);
}
public StarButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true);
}
/**
* set image resource and sync image resource id.
*/
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setImageResourceId(resId);
}
public int getImageResourceId() {
return mLastResourceId;
}
public void setImageResourceId(int resId) {
mLastResourceId = resId;
}
}
I want to give the effect that the ListView has faded from whatever is around it. By default it is set to whatever colour your ListView is. I can adjust the orientation of the FadingEdge and the size of the FadingEdge but not the colour. Is it possible?
You'll need to create a new class that extends ListView.
package com.mypackage;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class ColorFadeListView extends ListView
{
// fade to green by default
private static int mFadeColor = 0xFF00FF00;
public ColorFadeListView(Context context, AttributeSet attrs)
{
this(context, attrs,0);
}
public ColorFadeListView(Context context, AttributeSet attrs, int defStyle)
{
super(context,attrs,defStyle);
setFadingEdgeLength(30);
setVerticalFadingEdgeEnabled(true);
}
#Override
public int getSolidColor()
{
return mFadeColor;
}
public void setFadeColor( int fadeColor )
{
mFadeColor = fadeColor;
}
public int getFadeColor()
{
return mFadeColor;
}
}
You can use this list view identically to a normal ListView (though you'll have to cast it properly to use the fadeColor accessor methods). In your XML, instead of defining an object as <ListView android:properties.../> define it as <com.mypackage.ColorFadeListView android:properties.../>
Yes you can !
setCacheColorHint(Color.WHITE);
You can try this (it's a hack, I know):
int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = context.getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(brandColor, PorterDuff.Mode.MULTIPLY);
I took advantage of the fact that the glow effect is actually a shared Drawable and applied a filter on it: http://evendanan.net/android/branding/2013/12/09/branding-edge-effect/