Android Text should appear both side in the Switch - android

I using custom switch for support of API 8. I am using THIS Libarary for Custom Switch. But I want to make something Like show in figure.I have tried to change the color ,though changing the color in the style but doesn't effect as i want.
Please help me , Thanks in advance.

Here's a full, working solution, after a fun day implementing this.
Use the following to set the drawable for the track of the switch. The track is the container within which the thumb slides left and right.
mMessengerSwitch.setTrackDrawable(new SwitchTrackTextDrawable(this,
"LEFT", "RIGHT"));
Here's the implementation of the SwitchTrackTextDrawable, which writes the text in the background exactly in the right position (well, I've only tested it for API 23 on a Nexus 5):
/**
* Drawable that generates the two pieces of text in the track of the switch, one of each
* side of the positions of the thumb.
*/
public class SwitchTrackTextDrawable extends Drawable {
private final Context mContext;
private final String mLeftText;
private final String mRightText;
private final Paint mTextPaint;
public SwitchTrackTextDrawable(#NonNull Context context,
#StringRes int leftTextId,
#StringRes int rightTextId) {
mContext = context;
// Left text
mLeftText = context.getString(leftTextId);
mTextPaint = createTextPaint();
// Right text
mRightText = context.getString(rightTextId);
}
private Paint createTextPaint() {
Paint textPaint = new Paint();
//noinspection deprecation
textPaint.setColor(mContext.getResources().getColor(android.R.color.white));
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// Set textSize, typeface, etc, as you wish
return textPaint;
}
#Override
public void draw(Canvas canvas) {
final Rect textBounds = new Rect();
mTextPaint.getTextBounds(mRightText, 0, mRightText.length(), textBounds);
// The baseline for the text: centered, including the height of the text itself
final int heightBaseline = canvas.getClipBounds().height() / 2 + textBounds.height() / 2;
// This is one quarter of the full width, to measure the centers of the texts
final int widthQuarter = canvas.getClipBounds().width() / 4;
canvas.drawText(mLeftText, 0, mLeftText.length(),
widthQuarter, heightBaseline,
mTextPaint);
canvas.drawText(mRightText, 0, mRightText.length(),
widthQuarter * 3, heightBaseline,
mTextPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}

Try using
android:textOn="On"
android:textOff="Off"
instead of
android:text="On"
in switches.
You can also go through this if it helps.

After struggling to find the right solution for this, I found this neat little library. I found it easy to use and it met my needs perfectly. It can even be used to display more than 2 values.
UPDATE: In the meanwhile this library has stopped being maintained, so you may want to try the one they recommend.
This is how I made it look eventually with some more styling, like white border which I put around a FrameLayout that wraps it (I needed to make it look exactly like this, you need not use border):
Here's the xml for this:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="1dp"
android:background="#drawable/white_border">
<belka.us.androidtoggleswitch.widgets.ToggleSwitch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:activeBgColor="#color/white"
custom:activeTextColor="#color/black"
custom:inactiveBgColor="#color/black"
custom:inactiveTextColor="#color/white"
custom:textToggleLeft="left"
custom:textToggleRight="right"/>
</FrameLayout>
And #drawable/white_border looks like this:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#android:color/transparent" />
<stroke android:width="2dip"
android:color="#color/white" />
<corners
android:radius="3dp"/>

I created a custom layout that contain a linear layout (will be used as a track of the switch) in this layout I placed two texts to simulate the track "on"/"off" texts, and on top of it, it has a regular switch but without a track, just a thumb with transparent track.
Anyway this is the code:
colors.xml
<color name="switch_selected_text_color">#FFFFFF</color>
<color name="switch_regular_text_color">#A8A8A8</color>
settings_switch_color_selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/switch_selected_text_color" android:state_checked="true" />
<item android:color="#color/switch_regular_text_color" />
</selector>
styles.xml
<style name="SwitchTextAppearance" parent="#android:style/TextAppearance.Holo.Small">
<item name="android:textColor">#color/settings_switch_color_selector</item>
</style>
new_switch.xml - used in the custom view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/track_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/settings_track"
android:weightSum="1">
<TextView
android:id="#+id/left_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:textColor="#color/switch_regular_text_color"
android:layout_weight="0.5"
android:gravity="center"
android:text="OFF" />
<TextView
android:id="#+id/right_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:textColor="#color/switch_regular_text_color"
android:layout_weight="0.5"
android:gravity="center"
android:text="ON" />
</LinearLayout>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:thumb="#drawable/thumb_selector"
android:switchTextAppearance="#style/SwitchTextAppearance"
android:textOn="ON"
android:textOff="OFF"
android:checked="true"
android:showText="true"
android:track="#android:color/transparent"/>
</RelativeLayout>
this is custom view - it`s just for inflating the custom view layout
public class DoubleSidedSwitch extends RelativeLayout {
private TextView _leftTextView;
private TextView _rightTextView;
private Switch _switch;
public DoubleSidedSwitch(Context context) {
super(context);
init(context);
}
public DoubleSidedSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
View view = LayoutInflater.from(context).inflate(R.layout.new_switch, this, true);
initViews(view);
initListeners();
}
private void initListeners() {
}
private void initViews(View view) {
}
}

There is one made by 2 Standard buttons and a LinearLayout. There are bunch of xml files to import but it works perfect on all versions and very easy to use. Check the following Github Page
Custom Switch With 2 Buttons
usage
Copy XML files under res/drawable to your project's res/drawable folder.
Copy LinearLayout from layout.xml to your layout file.
Copy values from values/colors.xml and values/dimens to your own files.
Initilize the switch with following code
SekizbitSwitch mySwitch = new SekizbitSwitch(findViewById(R.id.sekizbit_switch));
mySwitch.setOnChangeListener(new SekizbitSwitch.OnSelectedChangeListener() {
#Override
public void OnSelectedChange(SekizbitSwitch sender) {
if(sender.getCheckedIndex() ==0 )
{
System.out.println("Left Button Selected");
}
else if(sender.getCheckedIndex() ==1 )
{
System.out.println("Right Button Selected");
}
}
});

Related

How to remove end padding of checkable menu item?

I have an overflow menu with a checkable menu item. I would like to align the CheckBox all the way to the end, but I don't know how this can be done.
My menu is defined like so:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:icon="#drawable/ic_baseline_more_vert_24"
android:title=""
app:showAsAction="always">
<menu>
<item
android:id="#+id/desktop_site"
android:checkable="true"
android:checked="false"
android:icon="#drawable/ic_baseline_desktop_windows_24"
android:title="Desktop site"
app:showAsAction="never" />
</menu>
</item>
</menu>
You can create a custom style to the CheckBox and adjust the end padding with a negative value:
<style name="checkbox_style" parent="android:style/Widget.Holo.Light.CompoundButton.CheckBox">
<item name="android:paddingRight">-7dp</item>
</style>
And apply it to your app's theme:
<item name="checkboxStyle">#style/checkbox_style</item>
Note: By default there should be some margin surrounds a menu item; so you can't send the checkBox to the far end, as its right edge will cut:
When using -8dp:
So, you need to be careful in handling this.
UPDATE
but it affects all the CheckBoxes in my app
This requires to reverse this padding in all of CheckBoxes in your layouts; and there are options for this:
First option: add android:paddingRight="7dp" / android:paddingEnd="7dp" to all the CheckBoxes.
Second option: Create a custom CheckBox class and add this padding to its constructors; and use this customized CheckBox instead of the default CheckBox:
public class MyCheckBox extends AppCompatCheckBox {
public MyCheckBox(Context context) {
super(context);
init();
}
public MyCheckBox(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
int px = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
7f, // dp value
getResources().getDisplayMetrics()
);
setPadding(0, 0, px, 0);
}
public MyCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
}
And to use it:
<!-- Add the full path of the customized CheckBox -->
<com.example.android......MyCheckBox
android:id="#+id/checkbox2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Hint: If you target API-17+, then use android:paddingEnd instead android:paddingRight
You can use a custom PopupWindow. This allows you to completely customize it.
First create a XML file of your Popup. Using wrap_content at the main layout is very important to shrink the Popup to its size.
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#000000"
android:padding="5dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_baseline_desktop_mac_24"
app:tint="#FFFFFF"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Desktop site"
android:textColor="#FFFFFF"
android:layout_marginLeft="10dp"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#FFFFFF"
android:minWidth="0dp"
android:minHeight="0dp"
android:layout_marginLeft="30dp"/>
</LinearLayout>
</LinearLayout>
Then show the dialog by calling this function. (I copied this code from one of my projects, but I can remember I got it from SO).
private void showPopup(Context context, Point p) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = layoutInflater.inflate(R.layout.your_popup_xml, null);
PopupWindow changeStatusPopUp = new PopupWindow(context);
changeStatusPopUp.setContentView(layout);
changeStatusPopUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
changeStatusPopUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
changeStatusPopUp.setAnimationStyle(android.R.style.Animation_Dialog);
int OFFSET_X = -300; //Adjust position of the Popup
int OFFSET_Y = 50; //Adjust position of the Popup
changeStatusPopUp.setOutsideTouchable(true);
changeStatusPopUp.setFocusable(true);
changeStatusPopUp.setBackgroundDrawable(new BitmapDrawable());
changeStatusPopUp.showAtLocation(layout, Gravity.NO_GRAVITY, p.x + OFFSET_X, p.y + OFFSET_Y);
}
If you want to catch Click Events, you just need to set an id to the LinearLayout and use it with the view in Java (LinearLayout ll = view.findViewById(R.id.yourId);).
Call the void by:
int[] location = new int[2];
view.getLocationOnScreen(location);
Point point = new Point();
point.x = location[0];
point.y = location[1];
showStatusPopup(context, point);
When using MaterialComponents some of the padding is from the checkbox itself. This can be removed by setting android:minWidth to 0. The remaining padding is symmetrical and looks good.
Like the accepted solution, create a style:
<style name="checkbox_style" parent="#style/Widget.MaterialComponents.CompoundButton.CheckBox">
<item name="minWidth">0dp</item>
</style>
And apply it to your app's theme:
<item name="checkboxStyle">#style/checkbox_style</item>

Android: how to configure sizing of a custom layout

I've been trying to create a custom horizontal layout with the goal to have a TextView to the left of an ImageView, containing a icon which depicts a certain status. The ImageView is to kept in a square dimension, with it's height and width equal to the height of the text in the TextView. Issues continue to persist, however, such as the text height not being set as specified in the layout xml file and an unknown padding existing after the ImageView. These problem can be seen in this image, with the red indicating the unknown padding and the blue indicating the text size inconsistency where both where set to 12sp. The font sizing and padding issues need to be fixed so the layout can be properly added to a grid layout, which will contain a grid of these custom layouts.
StatusIcon.java
//This is part of the java class that extends ImageView to resize the Icon
#Override
protected void onMeasure(int width, int height) {
super.onMeasure(width, height);
int measuredHeight = getMeasuredHeight();
setMeasuredDimension(measuredHeight, measuredHeight);
}
StatusIndicator.java
//This is the java class for the custom layout.
public class StatusIndicator extends LinearLayout {
private TextView label;
private StatusIcon statusLed;
private CharSequence labelText;
private float labelTextSize;
public enum Status {
GOOD,
WARNING,
CRITICAL
}
/*
* Removed the basic required class constructors to save space.
*/
private void getAttributes(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StatusIndicator);
labelText = typedArray.getString(R.styleable.StatusIndicator_label);
labelTextSize = typedArray.getDimensionPixelSize(R.styleable.StatusIndicator_labelSize, 0);
typedArray.recycle();
}
private void initializeViews(Context context){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_status_indicator, this);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
//Setup UI elements in layout
label = (TextView) findViewById(R.id.textView_statusIndicatorLabel);
statusLed = (StatusIcon) findViewById(R.id.imageView_statusIndicatorLed);
label.setText(labelText);
if(labelTextSize > 0){
label.setTextSize(TypedValue.COMPLEX_UNIT_SP, labelTextSize);
}
}
public void setStatus(StatusIndicator.Status status){
switch (status){
case GOOD:
statusLed.setImageResource(R.mipmap.ic_status_panel_good);
break;
case WARNING:
statusLed.setImageResource(R.mipmap.ic_status_panel_warning);
break;
case CRITICAL:
statusLed.setImageResource(R.mipmap.ic_status_panel_critical);
break;
}
}
}
view_status_indicator.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="LinearLayout"
tools:orientation="horizontal">
<TextView
android:id="#+id/textView_statusIndicatorLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|start"
android:layout_marginEnd="2dp"
android:text="#string/default_title"
android:textAppearance="#style/TextAppearance.AppCompat.Title"
android:textSize="12sp"/>
<com.css_design.android_quickbridge.ui.home.status_panel.StatusIcon
android:id="#+id/imageView_statusIndicatorLed"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center_vertical|end"
app:srcCompat="#mipmap/ic_status_panel_critical"/>
</merge>
I would solve this problem by using ConstraintLayout instead of creating a custom view implementation.
ConstraintLayout allows you to specify an aspect ratio for its children, which takes care of wanting to make sure your ImageView is always exactly square. ConstraintLayout also allows you to specify height or width based on sibling views (by combining a dimension of 0dp with top and bottom (or left and right) constraints).
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ccf">
<ImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="#drawable/circle"
app:layout_constraintTop_toTopOf="#+id/text"
app:layout_constraintLeft_toRightOf="#+id/text"
app:layout_constraintBottom_toBottomOf="#+id/text"
app:layout_constraintDimensionRatio="1"/>
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40sp"
android:text="hello world"/>
</android.support.constraint.ConstraintLayout>
(Background color added to the ConstraintLayout to show that it's not any larger than its contents).

How to represent circle border with multiple colors in android

I am looking for a custom widget to draw a circle with multiple border colors.
Say for example if my total circle represent 0-360, I need to color my circle border with different colors.
For example, I need to mark 0-60 with red, 61-120 with green, 121-300 with magenta and 301-360 with yellow border color.
please suggest me how I can do it in android.
You application is pretty simple. I don't recommend your using an external library. You can quickly implement a class that draws and manages your desired shape. An example is presented:
public class DifferentColorCircularBorder{
private RelativeLayout parentLayout;
public DifferentColorCircularBorder(RelativeLayout parentLayout) {
this.parentLayout = parentLayout;
}
public void addBorderPortion(Context context, int color, int startDegree, int endDegree) {
ProgressBar portion = getBorderPortion(context, color, startDegree, endDegree);
parentLayout.addView(portion);
}
private ProgressBar getBorderPortion(Context context, int color, int startDegree, int endDegree) {
LayoutInflater inflater = LayoutInflater.from(context);
ProgressBar portion = (ProgressBar) inflater.inflate(R.layout.border_portion, parentLayout, false);
portion.setRotation(startDegree);
portion.setProgress(endDegree - startDegree);
portion.getProgressDrawable().setColorFilter(color, Mode.SRC_ATOP);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) portion.getLayoutParams();
params.addRule(RelativeLayout.CENTER_IN_PARENT);
portion.setLayoutParams(params);
return portion;
}
}
border_portion is defined as below:
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="220dp"
android:layout_height="220dp"
android:progressDrawable="#drawable/circle_exterior"
android:layout_centerInParent="true"
android:max="360"/>
circle_exterior is defined here:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadius="100dp"
android:thickness="10dp" >
<solid android:color="#ff111111" />
</shape>
The MainActivity class is defined like this:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout interiorLayout = (RelativeLayout) findViewById(R.id.interior);
DifferentColorCircularBorder border = new DifferentColorCircularBorder(interiorLayout);
border.addBorderPortion(getApplicationContext(), Color.RED, 0, 40);
border.addBorderPortion(getApplicationContext(), Color.GREEN, 40, 90);
border.addBorderPortion(getApplicationContext(), Color.BLUE, 90, 270);
border.addBorderPortion(getApplicationContext(), 0xFF123456, 270, 360);
}
}
finally activity_main layout is:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin" >
<RelativeLayout
android:id="#+id/interior"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<View
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#drawable/circle_interior_bg"
android:layout_centerInParent="true" />
</RelativeLayout>
</RelativeLayout>
Explanation about the dimensions: This is an example. Here, I have picked the dimensions to fit the circle perfectly. Change these based on your application.
Image sample:
i just created a simple Library for that purpose CircularStatusView , it was inspired by WhatsApp Status and it's easy to use.
first up add the view, in my case i've added it around CircleImageView but you can use on any view.
<RelativeLayout
android:id="#+id/image_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_centerInParent="true"
android:padding="6dp"
android:src="#mipmap/ic_launcher" />
<com.devlomi.circularstatusview.CircularStatusView
android:id="#+id/circular_status_view"
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_centerInParent="true"
app:portion_color="#color/colorAccent"
app:portion_spacing="4dp"
app:portion_width="4dp"
app:portions_count="8" />
</RelativeLayout>
you can set the portions count Programmatically by using:
circularStatusView.setPortionsCount(count);
and for the portions color:
circularStatusView.setPortionsColor(color);
you can also set specific color for every portion:
circularStatusView.setPortionColorForIndex(/*index of portions starting from first portion at the top CW */ i, color);
for this you can try this library that i had come across
https://github.com/mucahitsidimi/GaugeView might be useful.
uses a custom view of fixed lengths to render the circle by using canvas

Ripple effect on top of Image - Android

I've been experimenting with the ripple animation in my latest side project. I'm having some trouble finding an "elegant" solution to using it in certain situations for touch events. Namely with images, especially in list, grid, and recycle views. The animation almost always seems to animate behind the view, not the on top of it. This is a none issue in Buttons and TextViews but if you have a GridView of images, the ripple appears behind or below the actual image. Obviously this is not what I want, and while there are solutions that I consider to be a work around, I'm hoping there is something simpler i'm just unaware of.
I use the following code to achieve a custom grid view with images. I'll give full code CLICK HERE so you can follow along if you choose.
Now just the important stuff. In order to get my image to animate on touch I need this
button_ripple.xml
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/cream_background">
<item>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Pressed -->
<item
android:drawable="#color/button_selected"
android:state_pressed="true"/>
<!-- Selected -->
<item
android:drawable="#color/button_selected"
android:state_selected="true"/>
<!-- Focus -->
<item
android:drawable="#color/button_selected"
android:state_focused="true"/>
<!-- Default -->
<item android:drawable="#color/transparent"/>
</selector>
</item>
</ripple>
custom_grid.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<ImageView
android:id="#+id/sceneGridItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/button_ripple"
android:gravity="center_horizontal"/>
</LinearLayout>
activity_main.xml
<GridView
android:id="#+id/sceneGrid"
android:layout_marginTop="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:verticalSpacing="15dp"
android:numColumns="5" />
The line where all magic and problems occur is when I set the background. While this does in fact give me a ripple animation on my imageview, it animates behind the imageview. I want the animation to appear on top of the image. So I tried a few different things like
Setting the entire grid background to button_ripple.
<GridView
android:id="#+id/sceneGrid"
android:layout_marginTop="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:verticalSpacing="15dp"
android:background="#drawable/button_ripple"
android:numColumns="5" />
It does exactly what you'd think, now the entire grid has a semi transparent background and no matter what image i press the entire grid animates from the center of the grid. While this is kind of cool, its not what I want.
Setting the root/parent background to button_ripple.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="#drawable/button_ripple"
android:orientation="horizontal">
The area is now larger and fills the entire cell of the grid (not just the image), however it doesn't bring it to the front.
Changing custom_grid.xml to a RelativeLayout and putting two ImageViews on top of each other
custom_grid.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/gridItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" />
<ImageView
android:id="#+id/gridItemOverlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:background="#drawable/button_ripple" />
</RelativeLayout>
CustomGridAdapter.java
....
gridItemOverLay = (ImageView) findViewById(R.id.gridItemOverlay);
gridItemOverlay.bringToFront();
This works. Now the bottom ImageView contains my image, and the top animates, giving the illusion of a ripple animation on top of my image. Honestly though this is a work around. I feel like this is not how it was intended. So I ask you fine people, is there a better way or even a different way?
I liked android developer's answer so I decided to investigate how to do step 2 of his solution in code.
You need to get this piece of code from Jake Wharton here : https://gist.github.com/JakeWharton/0a251d67649305d84e8a
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class ForegroundImageView extends ImageView {
private Drawable foreground;
public ForegroundImageView(Context context) {
this(context, null);
}
public ForegroundImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView);
Drawable foreground = a.getDrawable(R.styleable.ForegroundImageView_android_foreground);
if (foreground != null) {
setForeground(foreground);
}
a.recycle();
}
/**
* Supply a drawable resource that is to be rendered on top of all of the child
* views in the frame layout.
*
* #param drawableResId The drawable resource to be drawn on top of the children.
*/
public void setForegroundResource(int drawableResId) {
setForeground(getContext().getResources().getDrawable(drawableResId));
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout.
*
* #param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (foreground == drawable) {
return;
}
if (foreground != null) {
foreground.setCallback(null);
unscheduleDrawable(foreground);
}
foreground = drawable;
if (drawable != null) {
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
}
requestLayout();
invalidate();
}
#Override protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == foreground;
}
#Override public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (foreground != null) foreground.jumpToCurrentState();
}
#Override protected void drawableStateChanged() {
super.drawableStateChanged();
if (foreground != null && foreground.isStateful()) {
foreground.setState(getDrawableState());
}
}
#Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (foreground != null) {
foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
invalidate();
}
}
#Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (foreground != null) {
foreground.setBounds(0, 0, w, h);
invalidate();
}
}
#Override public void draw(Canvas canvas) {
super.draw(canvas);
if (foreground != null) {
foreground.draw(canvas);
}
}
}
This is the attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ForegroundImageView">
<attr name="android:foreground"/>
</declare-styleable>
</resources>
Now, create your ForegroundImageView like so in your layout.xml:
<com.example.ripples.ForegroundImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:foreground="?android:selectableItemBackground"
android:src="#drawable/apples"
android:id="#+id/image" />
The image will now ripple.
Instead of trying to add the ripple to each individual view in the adapter, you can just add it at the GridView level like this:
<GridView
android:id="#+id/gridview"
...
android:drawSelectorOnTop="true"
android:listSelector="#drawable/your_ripple_drawable"/>
Maybe try one of these solutions (got the tip from here) :
Wrap the drawable in a RippleDrawable² before setting it on the ImageView:
Drawable image = …
RippleDrawable rippledImage = new RippleDrawable(
ColorStateList.valueOf(rippleColor), image, null);
imageView.setImageDrawable(rippledImage);
Extend ImageView and add a foreground attribute to it (like FrameLayout has³). See this example⁴ from +Chris Banes of adding it to a LinearLayout. If you do this then make sure you pass through the touch co-ordinates so that the ripple starts from the correct point:
#Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (foreground != null) {
foreground.setHotspot(x, y);
}
}
I have an image gallery (built with a RecyclerView and a GridLayoutManager). The image is set using Picasso. To add a ripple to the image I've wrapped the ImageView with a FrameLayout. The item layout is:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp"
android:foreground="#drawable/ripple"
android:clickable="true"
android:focusable="true"
>
<ImageView
android:id="#+id/grid_item_imageView"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_gravity="center"
android:scaleType="centerInside"
/>
</FrameLayout>
Note the android:foreground. It's not the same as android:background. I've tried without android:clickable="true" and android:focusable="true" and it also works, but it doesn't hurt.
Then add a ripple.xml drawable into res/drawable:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#7FF5AC8E" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#android:color/transparent" />
</shape>
</item>
</selector>
Note that this shows a semi-transparent color on top of the image when the item is selected (for devices < 5.0). You can remove it if you don't want it.
Then add the ripple.xml drawable with ripple into res/drawable-v21:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/coral">
</ripple>
You can use the default ripple effect instead of the custom ripple.xml, but it's really difficult to see on top of an image because it's grey:
android:foreground="?android:attr/selectableItemBackground"

ListItem should change bg color, color of text and images on it while pressed

I have a ListView, where each item has a custom LinearLayout with a bg image, a textView and 2 imageViews.
Now I need that while the user is touching the item, all of those switch to the "pressed" state:
the bg image of the LiearLayout must be replaced with another one
the TextView should change textColor
both ImageViews in the item should switch to alternative images
Normally such stuff would be done using an xml resource with selector inside, e.g. the LinearLayout would use a drawable with selector inside for background, the TextView a drawable with selector and colors for textColor, and ImageViews use selector with images inside for src.
The problem is that the pressed state is only detected by the LinearLayout and not by the child views (?), so only the background image changes.
I've tried implementing this using OnTouchListener, but then comes the problem that I can't securely get access to Views inside the list item.
I tried caching the view which I return in getView() of the list item to then later change the images and text color. This works usually, but e.g. if one of the list items opens another activity, then the view somehow gets lost and the highlighted state stays indefinitely. I've tried debugging and it works correctly if I step thru with the debugger.
Also, reusing the cachedView seems to bring no good and messes things up completely, so I'm just inflating a new view for the list item each time (this must be inefficient).
Just in case, here is the code of the custom list item item i'm using for the custom list adapter:
public class MyListItem extends AbstractListItem
{
private int iconResource, iconHighlightedResource;
private int textResource;
private View.OnClickListener onClickListener;
private LinearLayout currentView;
private ImageView imgIcon;
private TextView txtText;
private ImageView imgArrow;
private boolean bIsHighlighted;
public MyListItem(int iconResource, int iconHighlightedResource, int textResource, View.OnClickListener onClickListener)
{
this.iconResource = iconResource;
this.iconHighlightedResource = iconHighlightedResource;
this.textResource = textResource;
this.onClickListener = onClickListener;
}
public View getView(View cachedView)
{
this.currentView = buildView();
populateView();
update();
return this.currentView;
}
private LinearLayout buildView()
{
LayoutInflater inflater = (LayoutInflater)App.get().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return (LinearLayout)inflater.inflate(R.layout.my_menu_item, null);
}
private void populateView()
{
this.imgIcon = (ImageView)this.currentView.findViewById(R.id.img_menu_item_icon);
this.txtText = (TextView)this.currentView.findViewById(R.id.txt_menu_item_text);
this.txtText.setText(this.textResource);
this.txtText.setTypeface(App.fontCommon);
this.imgArrow = (ImageView)this.currentView.findViewById(R.id.img_menu_item_arrow);
this.currentView.setOnClickListener(this.onClickListener);
this.currentView.setOnTouchListener(this.highlighter);
}
private View.OnTouchListener highlighter = new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
int nAction = event.getAction();
int nActionCode = nAction & MotionEvent.ACTION_MASK;
switch (nActionCode)
{
case MotionEvent.ACTION_DOWN:
bIsHighlighted = true;
update();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
bIsHighlighted = false;
update();
break;
}
return false;
}
};
private void update()
{
if (this.bIsHighlighted)
{
updateForHighlightedState();
}
else
{
updateForNormalState();
}
}
private void updateForHighlightedState()
{
Resources r = App.get().getResources();
this.currentView.setBackgroundResource(R.drawable.button_beveled_m_call_to_action_taking_input);
this.imgIcon.setImageResource(this.iconHighlightedResource);
this.txtText.setTextColor(r.getColor(R.color.white));
this.imgArrow.setImageResource(R.drawable.arrow_highlighted);
}
private void updateForNormalState()
{
Resources r = App.get().getResources();
this.currentView.setBackgroundColor(r.getColor(R.color.white));
this.imgIcon.setImageResource(this.iconResource);
this.txtText.setTextColor(r.getColor(R.color.text_dark));
this.imgArrow.setImageResource(R.drawable.arrow);
}
}
Here is the layout file (xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#color/white"
android:gravity="center_vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/img_menu_item_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/info" />
<TextView
android:id="#+id/txt_menu_item_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="24dp"
android:text="Menu item"
android:textColor="#color/text_dark"
android:layout_marginLeft="5dp" />
<ImageView
android:id="#+id/img_menu_item_arrow"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/arrow" />
</LinearLayout>
After lots of experimenting finally this worked:
Every child view inside the list item layout must have android:duplicateParentState="true".
Then all of them can just use selector drawables. No extra effort inside the code is required.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#drawable/my_item_bg"
android:gravity="center_vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/img_menu_item_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/selector_info"
android:duplicateParentState="true" />
<TextView
android:id="#+id/txt_menu_item_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="24dp"
android:text="Menu item"
android:textColor="#drawable/selector_color_my_button_text"
android:layout_marginLeft="5dp"
android:duplicateParentState="true" />
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/selector_arrow"
android:duplicateParentState="true" />
</LinearLayout>
You should create custom drawable selectors, and set them as the background to your listview element.
Step#1, create another layout (named: layout_selected for this example), with the appropriate background color for your pressed state (like the layout file you supplied, but with the background attribute of the linear set to another color).
Then you will define a drawable selector, which will be placed in your drawable folder), defining which background should be use in which instance. This will look something like this:
<!-- pressed -->
<item android:drawable="#drawable/layout_selected" android:state_pressed="true"/>
<!-- focused -->
<item android:drawable="#drawable/layout_normal" android:state_focused="true"/>
<!-- default -->
<item android:drawable="#drawable/layout_normal"/>
Finally, to use this in your list, when you set the layout for your adapter, set it to the selector we just created, instead of your standard layout.
Maybe a little hard to explain, but you want to use 'Drawable Selectors' to accomplish what you want.
I would suggest to add ViewHolder pattern for listview. This will optimize your listview drawing & creating UI.
Also in that we can use setTag to save instance of row. In that you can handle touch event.

Categories

Resources