How to customize this drawable with parameters in Android? - android

This is the xml of "mydrawable" which I'm using as background for buttons
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false" android:state_pressed="false" >
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="-45"
android:endColor="#color/colorPrimary700"
android:startColor="#color/colorPrimary600"
android:type="linear" />
<corners android:radius="#dimen/ic_button_corner"></corners>
</shape>
</item>
<item android:state_focused="false" android:state_pressed="true" >
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="-45"
android:endColor="#color/colorPrimary800"
android:startColor="#color/colorPrimary700"
android:type="linear" />
<corners android:radius="#dimen/ic_button_corner"></corners>
</shape>
</item>
</selector>
This is a use case
<Button
android:id="#+id/login_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/mydrawable"
android:text="#string/log_in"/>
How to port this static drawable into a custom view with configurable parameters like the colors used in the gradients for each item and the size corner radius?
For example via Java
MyDrawable myDrawable=new MyDrawable();
myDrawable.setGradientColors(color1, color2);
myDrawable.setCornerRadius(size);
button.setBackground(Drawable);
Is it also possible via custom Button (MyButton, instead of MyDrawable)?
<MyButton
parameter_gradientcolor1:#color/color1
parameter_gradientcolor2:#color/color2
... />
EDIT
This is not working, neither reactions to click event nor correct gradient
public class SelectorButton extends AppCompatButton {
StateListDrawable mStateListDrawable;
public SelectorButton(Context context, AttributeSet attrs) {
super(context, attrs);
float cornerRadius = attrs.getAttributeFloatValue("app", "cornerRadius", 0);
int normalStartColor = attrs.getAttributeIntValue("app", "normalStartColor", R.color.mds_grey_400);
int normalEndColor = attrs.getAttributeIntValue("app", "normalEndColor", R.color.mds_grey_500);
int pressedStartColor = attrs.getAttributeIntValue("app", "pressedStartColor", R.color.mds_grey_400);
int pressedEndColor = attrs.getAttributeIntValue("app", "pressedEndColor", R.color.mds_grey_500);
GradientDrawable normalDrawable = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{normalStartColor, normalEndColor});
normalDrawable.setCornerRadius(cornerRadius);
GradientDrawable pressedDrawable = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{pressedStartColor, pressedEndColor});
pressedDrawable.setCornerRadius(cornerRadius);
mStateListDrawable = new StateListDrawable();
mStateListDrawable.addState(new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused},
normalDrawable);
mStateListDrawable.addState(new int[]{android.R.attr.state_pressed, -android.R.attr.state_focused},
pressedDrawable);
setBackground(mStateListDrawable);
}
}
This is in the layout
<com.utils.views.SelectorButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:clickable="true"
app:normalEndColor="#color/mds_blue_400"
app:normalStartColor="#color/mds_red_500"
app:pressedEndColor="#color/mds_amber_500"
app:pressedStartColor="#color/mds_green_300" />

Yes, you can do this via custom button. Here is a code snippet.
public class SelectorButton extends AppCompatButton {
StateListDrawable mStateListDrawable;
public SelectorButton(Context context, AttributeSet attrs) {
super(context, attrs);
mStateListDrawable = new StateListDrawable();
GradientDrawable normalDrawable = new GradientDrawable(yourColor);
normalDrawable.setCornerRadius(yourRadius);
mStateListDrawable.addState(
new int[]{-android.R.attr.state_pressed, -android.R.attr.state_enabled}, );
setBackground(mStateListDrawable);
}
}
In order to set style via XML, you can define custom style such as colors or corner radius in attrs.xml.If you have any question, feel free to ask.
EDIT
Now I will show you how to declare custom style in XMLand use them. For example, I want to set normal and pressed state gradient color.
In yourProject/app/src/main/res/values dir, create a new file called attrs.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SelectorButton">
<attr name="normalStartColor" format="color"/>
<attr name="normalEndColor" format="color"/>
<attr name="pressedStartColor" format="color"/>
<attr name="pressedEndColor" format="color"/>
</declare-styleable>
</resources>
As you see, I define four attributes.Now you can set these attributes via xml.
<SelectorButton
app:normalStartColor=""
app:normalEndColor=""
app:pressedStartColor=""
app:pressedEndColor=""
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
EDIT: obtain values from xml
Sorry for my mistakes. You can obtain these values like this.
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SelectorButton, 0, 0);
int normalStartColor = a.getColor(R.styleable.SelectorButton_normalStartColor, 0);
a.recycle();
And there is a pressed state. You can do like this.
mStateListDrawable.addState(new int[]{android.R.attr.state_pressed, -android.R.attr.state_enabled}, pressedDrawable);

Drawable in xml is just a drawable, so more like an image, you can not style it like that.
However, you can add custom style to your custom button MyButton.
That can be learned from over here https://developer.android.com/training/custom-views/create-view.html#customattr

Related

ColorStateList and TextColor not being applied using CustomAttr in ToggleButton in Android

I have a custom class called SkinableToggleButton which extends ToggleButton of Android. Its code is as below:
public class SkinableToggleButton extends ToggleButton {
public SkinableToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SkinableToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//Applied custom font/typeface here
init(context, attrs);
}
protected void init(Context context, AttributeSet attrs) {
if (isInEditMode()) return;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Skinable, 0, 0);
try {
if (a.hasValue(R.styleable.Skinable_skinableTextColor)) {
int textColorSkin = a.getInt(R.styleable.Skinable_skinableTextColor, 0);
SkinUtil.applyTextColorTheme(this, textColorSkin);//Applying custom text color through custom attr
}
if (a.hasValue(R.styleable.Skinable_skinableSrcTint)) {
int backgroundTintSkin = a.getInt(R.styleable.Skinable_skinableSrcTint, 0);
int checkedColor = SkinUtil.getColor(backgroundTintSkin);//util method returning correct color
int disabledColor = ContextCompat.getColor(getContext(), R.color.grey_3);
int[][] states = new int[][]{
new int[]{android.R.attr.state_checked}, // checked
new int[]{-android.R.attr.state_checked}, // unchecked
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
};
int[] colors = new int[]{
checkedColor,
checkedColor,
checkedColor,
disabledColor
};
compoundButton.setButtonTintList(new ColorStateList(states, colors));
}
} finally {
a.recycle();
}
}}
In XML, I have used it as follows:
<SkinableToggleButton
android:layout_width="wrap_content"
style="#style/tiny_toggle_button"
android:textOff="Connect"
android:textOn="Connected"/>
Style tiny_toggle_button code:
<style name="tiny_toggle_button">
<item name="android:background">#drawable/selector_toggle_btn</item>
<item name="android:layout_height">#dimen/spacing_5x</item>
<item name="android:textSize">#dimen/sp_12</item>
<item name="android:lineHeight">#dimen/sp_16</item>
<item name="android:letterSpacing">0.0175</item>
<item name="android:textColor">#color/selector_toggle_bg</item>
<item name="skinableSrcTint">primary1</item> <!--Custom Attr-->
</style>
selector_toggle_btn drawable:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:state_checked="false" android:state_enabled="true" >
<shape
android:shape="rectangle">
<solid android:color="#color/transparent"/>
<corners android:radius="#dimen/spacing_0_5x"/>
<stroke
android:width="1dp"
android:color="#color/black"
/>
</shape>
</item>
<item android:state_checked="true" android:state_enabled="true">
<shape android:shape="rectangle">
<solid android:color="#color/black"/>
<corners android:radius="#dimen/spacing_0_5x"/>
</shape>
</item>
<item android:state_enabled="false">
<shape
android:shape="rectangle">
<solid android:color="#color/white_1"/>
<corners android:radius="#dimen/spacing_0_5x"/>
<stroke
android:width="1dp"
android:color="#color/grey_4"/>
</shape>
</item>
</selector>
selector_toggle_bg in color:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:state_enabled="true" android:state_checked="true" android:color="#color/white_1"/>
<!-- Default State -->
<item android:state_enabled="true" android:state_checked="false"
android:color="#color/black"/>
<item android:color="#color/grey_3" android:state_enabled="false"/>
</selector>
Problem:
My custom attrs (Tint colors and text colors) are not being applied from selector drawable and color files. My drawable and text color selector is working perfectly fine except for my custom attrs. Am I doing something wrong with the custom attrs and applying ColorStateList? I have set my custom attr skinableSrcTint in my custom style in styles.xml. Custom style's code has already been pasted above.
I am not very much experienced at posting questions on StackOverflow. Please let me know if any other detail is required, I will edit it and post detail/or in comments.

Using android:drawable in a ColorStateList

When creating a ColorStateList for a share button, I first used android:drawable to specify an item color like so (by accident)
<item android:state_pressed="true" android:drawable="#color/stock_orange"/>
instead of android:color (like "normal")
<item android:state_pressed="true" android:color="#color/stock_orange"/>
Though there wasn't a crash and a color change occurred when pressed, it was the wrong color (magenta instead of the specified orange).
Is there an obvious explanation for this? Can/should drawables be used in a color state list?
Resource/Color/share_btn_color_state_list.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#A0A0A0"/>
<!--QUESTION: works, but color is magenta, not orange -->
<!--item android:state_pressed="true" android:drawable="#color/stock_orange"/-->
<item android:state_pressed="true" android:color="#color/stock_orange"/>
<item android:color="#color/black"/>
</selector>
Resource/Layout/share_view.xml
<ImageView
android:id="#+id/share_btn_iv"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#drawable/ic_share_black_24dp"/>
Usage
_shareButton = FindViewById<ImageView>(Resource.Id.share_btn_iv);
_shareButton.Enabled = true;
_shareButton.Drawable.SetTintList(
ColorStateList.CreateFromXml(
Resources,
Resources.GetXml(Resource.Color.share_btn_color_state_list)
)
);
drawable in a ColorStateList
One way is to customize an ImageView and use a combination of a ColorFilter and a ColorStateList that contains your tint color for when the button is pressed.
Extend ImageView and wrap DrawableStateChanged() with code that sets the tint based on the new state :
public class TintableImageView : ImageView
{
private ColorStateList tint;
public TintableImageView(Context context) : base(context)
{
}
public TintableImageView(Context context, IAttributeSet attrs):base(context,attrs)
{
init(context, attrs, 0);
}
public TintableImageView(Context context, IAttributeSet attrs, int defStyle):base(context,attrs,defStyle)
{
init(context, attrs, defStyle);
}
private void init(Context context, IAttributeSet attrs, int defStyle)
{
TypedArray a = context.ObtainStyledAttributes(attrs, Resource.Styleable.TintableImageView, defStyle, 0);
tint = a.GetColorStateList(Resource.Styleable.TintableImageView_tint);
a.Recycle();
}
protected override void DrawableStateChanged()
{
base.DrawableStateChanged();
if(tint != null && tint.IsStateful)
{
UpdateTintColor();
}
}
public void SetColorFilter(ColorStateList tint)
{
this.tint = tint;
base.SetColorFilter(new Color(tint.GetColorForState(GetDrawableState(), new Color(0))));
}
private void UpdateTintColor()
{
var color = new Color(tint.GetColorForState(GetDrawableState(), new Color(0)));
SetColorFilter(color);
}
}
Define a custom attribute :
attrs.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="TintableImageView">
<attr name="tint" format="reference|color" />
</declare-styleable>
</resources>
Color selector like this : Resource\Color\color_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#color/colorAccent"/>
<item android:color="#00000000"/>
</selector>
When use this custom ImageView ;
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageDemo.TintableImageView
android:id="#+id/myImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/download"
app:tint="#color/color_selector"
android:clickable="true"/>
</LinearLayout>
EDIT :
For example, add a translucent color as the color effect :
Resource\Color\color_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#55000000"/>
<item android:color="#00000000"/>
</selector>
Effect :
Usually the color state list are used to change the color of any ui element for different states For example, a Button widget can exist in one of several different states (pressed, focused, or neither) and, using a color state list, you can provide a different color during each state.
And a StateListDrawable is a drawable object defined in XML that uses a several different images to represent the same graphic, depending on the state of the object. For example, a Button widget can exist in one of several different states (pressed, focused, or neither) and, using a state list drawable, you can provide a different background image for each state.
Though what you did worked but it is not a recommended approach

Pressed State Not Being Selected in ImageButton Tint List

I have a view class TintSelectorImageButton that applies a tint using DrawCompat::setTintList:
public class TintSelectorImageButton extends ImageButton{
// Actual resource values seem to be fairly large and positive, so -1 should be a safe sentinel
protected static final int NO_TINT = -1;
public TintSelectorImageButton(Context context, AttributeSet attrs){
super(context, attrs);
TypedArray args = context.obtainStyledAttributes(attrs, R.styleable.TintSelectorImageButton);
if(args.hasValue(R.styleable.TintSelectorImageButton_tintList)){
int colorStates = args.getResourceId(R.styleable.TintSelectorImageButton_tintList, NO_TINT);
if(colorStates != NO_TINT){
ColorStateList colorStateRes = ContextCompat.getColorStateList(context, colorStates);
Drawable wrappedDrawable = DrawableCompat.wrap(getDrawable().mutate());
DrawableCompat.setTintList(wrappedDrawable, colorStateRes);
setImageDrawable(wrappedDrawable);
}
}
args.recycle();
}
}
I am providing it a ColorStateList through the defined xml property that looks like this one:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:color="#color/sheet_toolbar_disabled"
android:state_enabled="false"/>
<item
android:color="#color/sheet_nav_clicked"
android:state_pressed="true"/>
<item
android:color="#color/sheet_toolbar_enabled"/>
</selector>
Unfortunately, the pressed state is not actually showing when I touch the button, but click events are being passed properly to the provided View.OnClickListener:
mPrevSheetButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
showSheet(mPrevSheetUid);
}
});
To get things to work for now, I just took the image and colored it in an image editor and used this selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/button_prev_light_blue"
android:state_pressed="true"/>
<item
android:drawable="#drawable/button_prev_disabled"
android:state_enabled="false"/>
<item
android:drawable="#drawable/button_prev"/>
</selector>
Oddly, the drawable selector reaches the "pressed" state without issue.
To make this work with a ColorStateList change the background of your ImageButton to ?android:attr/selectableItemBackground or ?android:attr/selectableItemBackgroundBorderless.
For example,
<ImageButton
android:id="#+id/imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="#drawable/button" />

Setting Gradient Background programmatically

I have an image on which I'm putting a colored overlay, like this (the colors are taken from here):
layout/list_item_view.xml
<View
android:id="#+id/image_cover_gradient"
android:layout_width="fill_parent"
android:layout_height="80dip"
android:layout_alignParentTop="true"
android:layout_marginTop="70dp"
android:background="#drawable/gradient_blue"
/>
drawable/gradient_blue.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#color/CornflowerBlue"
android:endColor="#color/Transparent"
android:type="linear" />
</shape>
</item>
</selector>
This always puts a blue overlay (CornflowerBlue) and it works as expected.
Now I'm trying to do this programatically and followed some stackoverflow answers (such as this), but still can't make it work. Here's my code:
private void setColor(int color){
View gradientCover = view.findViewById(R.id.image_cover_gradient);
// this FAILS because it's a StateListDrawable
//GradientDrawable coverGd = (GradientDrawable) gradientCover.getBackground();
//coverGd.setColor(color);
//this doesn't seem to work either (I don't see any effect on the image)
GradientDrawable drawable = new GradientDrawable(
Orientation.BOTTOM_TOP, new int[] { color, resources.getColor(R.color.Transparent)
});
StateListDrawable sld = new StateListDrawable();
sld.addState(new int[] { android.R.attr.startColor, android.R.attr.endColor}, drawable);
gradientCover.setBackground(sld);
}
As #pskink suggested - removing the StateListDrawable solved it:
GradientDrawable drawable = new GradientDrawable(
Orientation.BOTTOM_TOP, new int[] { color, resources.getColor(R.color.Transparent)
});
gradientCover.setBackground(drawable);

How to use Custom Selector Styles on a TextView

I'm trying to figure out how to add an outer glow to a TextView when it's touched. The approach I'm working with is to use a Selector, but it doesn't seem to be working.
I've got the following structure
layout\HomeView.axml
<TextView
android:id="#+id/textview1"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
style="#drawable/control_selector_state" />
drawable\control_selector_state.xml
<!-- yes these are all the same for testing purposes -->
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
style="#style/control_style_focused"/>
<item android:state_focused="true"
android:state_pressed="true"
style="#style/control_style_focused" />
<item android:state_pressed="true"
style="#style/control_style_focused" />
<item style="#style/control_style_focused" />
</selector>
values\styles.xml
<resources>
<style name="control_style_focused">
<item name="android:shadowColor">#0000ff</item>
<item name="android:textColor">#ff0000</item>
<item name="android:shadowDx">0.0</item>
<item name="android:shadowRadius">8</item>
</style>
</resources>
The problem I'm having is that my TextView text is white, and the style doesn't seem to be applying.
How do I get this style to apply to my TextView?
so as #Longwayto said, selector styles are only available for drawables. That doesn't mean it's impossible.
Here's a working approach.
First you create a custom TextView that extends TextView
public class MyTextView: TextView
{
private readonly Context _context;
public FontIconTextView(Context context, IAttributeSet attrs) : base(context)
{
_context = context;
Initialize(attrs);
}
private void Initialize(IAttributeSet attrs)
{
var a = _context.Theme.ObtainStyledAttributes(attrs, Resource.Styleable.MyTextView, 0, 0);
_touchGlowColor = a.GetString(Resource.Styleable.MyTextView_TouchGlowColor);
_touchGlowSize = a.GetInteger(Resource.Styleable.MyTextView_TouchGlowSize, 0);
Initialize();
}
private void Initialize()
{
// other initialize stuff happens here...
}
private int _touchGlowSize;
private string _touchGlowColor;
public override bool OnTouchEvent(MotionEvent motionEvent)
{
if (Enabled)
{
var color = string.IsNullOrEmpty(_touchGlowColor) ? new Color(255, 255, 255, 255) : Color.ParseColor(_touchGlowColor);
switch (motionEvent.Action)
{
case MotionEventActions.Down:
SetShadowLayer(_touchGlowSize, 0, 0, color);
break;
case MotionEventActions.Up:
case MotionEventActions.Cancel:
SetShadowLayer(0, 0, 0, new Color(255, 255, 255, 255));
break;
}
}
return base.OnTouchEvent(motionEvent);
}
}
then, you have to go into your values directory and specify your custom attributes.
Resources\values\CustomBindingAttributes.xml
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="MyTextView">
<attr name="TouchGlowColor" format="string" />
<attr name="TouchGlowSize" format="integer" />
</declare-styleable>
</resources>
All of the above will be reusable across your entire application... no more duplicating shit on every view.
Lastly, here's how your button will look.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- obviously the foo/bar is just to show that you add android attrs like normal -->
<some.name.space.MyTextView
android:foo="foo"
amdroid:bar="bar"
local:TouchGlowColor="#66e400"
local:TouchGlowSize="20" />
</LinearLayout>
one gotcha I ran into is setting TouchGlowSize to 30 caused the app to crash. not sure why atm

Categories

Resources