I have added an image right of the text in an EditText widget, using the following XML:
<EditText
android:id="#+id/txtsearch"
...
android:layout_gravity="center_vertical"
android:background="#layout/shape"
android:hint="Enter place,city,state"
android:drawableRight="#drawable/cross" />
But I want to clear the EditText when the embedded image is clicked. How can I do this?
Actually you don't need to extend any class. Let's say I have an EditText editComment with a drawableRight
editComment.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (editComment.getRight() - editComment.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here
return true;
}
}
return false;
}
});
we getRawX() because we want to get the actual position of touch on screen, not relative to parent.
To get left side click
if(event.getRawX() <= (editComment.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width()))
Very, very good, thanks to everyone who contributed to this discussion. So if you don't want to deal with inconvenience of extending the class you can do the following (implemented for the right drawable only)
this.keyword = (AutoCompleteTextView) findViewById(R.id.search);
this.keyword.setOnTouchListener(new RightDrawableOnTouchListener(keyword) {
#Override
public boolean onDrawableTouch(final MotionEvent event) {
return onClickSearch(keyword,event);
}
});
private boolean onClickSearch(final View view, MotionEvent event) {
// do something
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
And here's bare-bone listener implementation based on #Mark's answer
public abstract class RightDrawableOnTouchListener implements OnTouchListener {
Drawable drawable;
private int fuzz = 10;
/**
* #param keyword
*/
public RightDrawableOnTouchListener(TextView view) {
super();
final Drawable[] drawables = view.getCompoundDrawables();
if (drawables != null && drawables.length == 4)
this.drawable = drawables[2];
}
/*
* (non-Javadoc)
*
* #see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
*/
#Override
public boolean onTouch(final View v, final MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && drawable != null) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final Rect bounds = drawable.getBounds();
if (x >= (v.getRight() - bounds.width() - fuzz) && x <= (v.getRight() - v.getPaddingRight() + fuzz)
&& y >= (v.getPaddingTop() - fuzz) && y <= (v.getHeight() - v.getPaddingBottom()) + fuzz) {
return onDrawableTouch(event);
}
}
return false;
}
public abstract boolean onDrawableTouch(final MotionEvent event);
}
Consider the following. It's not the most elegant solution but it works, I just tested it.
Create a customized EditText class CustomEditText.java:
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.EditText;
public class CustomEditText extends EditText
{
private Drawable dRight;
private Rect rBounds;
public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomEditText(Context context) {
super(context);
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top,
Drawable right, Drawable bottom)
{
if(right !=null)
{
dRight = right;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_UP && dRight!=null)
{
rBounds = dRight.getBounds();
final int x = (int)event.getX();
final int y = (int)event.getY();
//System.out.println("x:/y: "+x+"/"+y);
//System.out.println("bounds: "+bounds.left+"/"+bounds.right+"/"+bounds.top+"/"+bounds.bottom);
//check to make sure the touch event was within the bounds of the drawable
if(x>=(this.getRight()-rBounds.width()) && x<=(this.getRight()-this.getPaddingRight())
&& y>=this.getPaddingTop() && y<=(this.getHeight()-this.getPaddingBottom()))
{
//System.out.println("touch");
this.setText("");
event.setAction(MotionEvent.ACTION_CANCEL);//use this to prevent the keyboard from coming up
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable
{
dRight = null;
rBounds = null;
super.finalize();
}
}
Change your layout XML to this (where com.example is your actual project package name):
<com.example.CustomEditText
android:id="#+id/txtsearch"
…
android:layout_gravity="center_vertical"
android:background="#layout/shape"
android:hint="Enter place,city,state"
android:drawableRight="#drawable/cross"
/>
Finally, add this (or something similar) to your activity:
…
CustomEditText et = (CustomEditText) this.findViewById(R.id.txtsearch);
…
I might be a bit off with the calculation of the touch bounds for the nested drawable but you get the idea.
I hope this helps.
I created a useful abstract class DrawableClickListener which implements OnTouchListener.
In addition to the DrawableClickListener class, I also created 4 additional abstract classes which extend the DrawableClickListener class and handle the clicking of the drawable area for the correct quadrant.
LeftDrawableClickListener
TopDrawableClickListener
RightDrawableClickListener
BottomDrawableClickListener
Point to Consider
One thing to consider is that the images are not resized if done this way; thus the images must be scaled correctly before being put into the res/drawable folder(s).
If you define a LinearLayout containing an ImageView and a TextView, it's a lot easier to manipulate the size of the image being displayed.
activity_my.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="replace this with a variable"
android:textSize="30sp"
android:drawableLeft="#drawable/my_left_image"
android:drawableRight="#drawable/my_right_image"
android:drawablePadding="9dp" />
</RelativeLayout>
MyActivity.java
package com.company.project.core;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MyActivity extends Activity
{
#Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_my );
final TextView myTextView = (TextView) this.findViewById( R.id.myTextView );
myTextView.setOnTouchListener( new DrawableClickListener.LeftDrawableClickListener(myTextView)
{
#Override
public boolean onDrawableClick()
{
// TODO : insert code to perform on clicking of the LEFT drawable image...
return true;
}
} );
myTextView.setOnTouchListener( new DrawableClickListener.RightDrawableClickListener(myTextView)
{
#Override
public boolean onDrawableClick()
{
// TODO : insert code to perform on clicking of the RIGHT drawable image...
return true;
}
} );
}
}
DrawableClickListener.java
package com.company.project.core;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;
/**
* This class can be used to define a listener for a compound drawable.
*
* #author Matthew Weiler
* */
public abstract class DrawableClickListener implements OnTouchListener
{
/* PUBLIC CONSTANTS */
/**
* This represents the left drawable.
* */
public static final int DRAWABLE_INDEX_LEFT = 0;
/**
* This represents the top drawable.
* */
public static final int DRAWABLE_INDEX_TOP = 1;
/**
* This represents the right drawable.
* */
public static final int DRAWABLE_INDEX_RIGHT = 2;
/**
* This represents the bottom drawable.
* */
public static final int DRAWABLE_INDEX_BOTTOM = 3;
/**
* This stores the default value to be used for the
* {#link DrawableClickListener#fuzz}.
* */
public static final int DEFAULT_FUZZ = 10;
/* PRIVATE VARIABLES */
/**
* This stores the number of pixels of "fuzz" that should be
* included to account for the size of a finger.
* */
private final int fuzz;
/**
* This will store a reference to the {#link Drawable}.
* */
private Drawable drawable = null;
/* CONSTRUCTORS */
/**
* This will create a new instance of a {#link DrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this {#link DrawableClickListener}
* is associated with.
* #param drawableIndex
* The index of the drawable that this
* {#link DrawableClickListener} pertains to.
* <br />
* <i>use one of the values:
* <b>DrawableOnTouchListener.DRAWABLE_INDEX_*</b></i>
*/
public DrawableClickListener( final TextView view, final int drawableIndex )
{
this( view, drawableIndex, DrawableClickListener.DEFAULT_FUZZ );
}
/**
* This will create a new instance of a {#link DrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this {#link DrawableClickListener}
* is associated with.
* #param drawableIndex
* The index of the drawable that this
* {#link DrawableClickListener} pertains to.
* <br />
* <i>use one of the values:
* <b>DrawableOnTouchListener.DRAWABLE_INDEX_*</b></i>
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public DrawableClickListener( final TextView view, final int drawableIndex, final int fuzz )
{
super();
this.fuzz = fuzz;
final Drawable[] drawables = view.getCompoundDrawables();
if ( drawables != null && drawables.length == 4 )
{
this.drawable = drawables[drawableIndex];
}
}
/* OVERRIDDEN PUBLIC METHODS */
#Override
public boolean onTouch( final View v, final MotionEvent event )
{
if ( event.getAction() == MotionEvent.ACTION_DOWN && drawable != null )
{
final int x = (int) event.getX();
final int y = (int) event.getY();
final Rect bounds = drawable.getBounds();
if ( this.isClickOnDrawable( x, y, v, bounds, this.fuzz ) )
{
return this.onDrawableClick();
}
}
return false;
}
/* PUBLIC METHODS */
/**
*
* */
public abstract boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz );
/**
* This method will be fired when the drawable is touched/clicked.
*
* #return
* <code>true</code> if the listener has consumed the event;
* <code>false</code> otherwise.
* */
public abstract boolean onDrawableClick();
/* PUBLIC CLASSES */
/**
* This class can be used to define a listener for a <b>LEFT</b> compound
* drawable.
* */
public static abstract class LeftDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link LeftDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link LeftDrawableClickListener} is associated with.
*/
public LeftDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_LEFT );
}
/**
* This will create a new instance of a
* {#link LeftDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link LeftDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public LeftDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_LEFT, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getPaddingLeft() + drawableBounds.width() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>TOP</b> compound
* drawable.
* */
public static abstract class TopDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a {#link TopDrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this
* {#link TopDrawableClickListener} is associated with.
*/
public TopDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_TOP );
}
/**
* This will create a new instance of a {#link TopDrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this
* {#link TopDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public TopDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_TOP, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getPaddingTop() + drawableBounds.height() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>RIGHT</b> compound
* drawable.
* */
public static abstract class RightDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link RightDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link RightDrawableClickListener} is associated with.
*/
public RightDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_RIGHT );
}
/**
* This will create a new instance of a
* {#link RightDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link RightDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public RightDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_RIGHT, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getWidth() - view.getPaddingRight() - drawableBounds.width() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>BOTTOM</b> compound
* drawable.
* */
public static abstract class BottomDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link BottomDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link BottomDrawableClickListener} is associated with.
*/
public BottomDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_BOTTOM );
}
/**
* This will create a new instance of a
* {#link BottomDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link BottomDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public BottomDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_BOTTOM, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getHeight() - view.getPaddingBottom() - drawableBounds.height() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
}
Kotlin is a great language where each class could be extended with new methods. Lets introduce new method for EditText class which will catch clicks to right drawable.
fun EditText.onRightDrawableClicked(onClicked: (view: EditText) -> Unit) {
this.setOnTouchListener { v, event ->
var hasConsumed = false
if (v is EditText) {
if (event.x >= v.width - v.totalPaddingRight) {
if (event.action == MotionEvent.ACTION_UP) {
onClicked(this)
}
hasConsumed = true
}
}
hasConsumed
}
}
You can see it takes callback function as argument which is called when user clicks to right drawable.
val username = findViewById<EditText>(R.id.username_text)
username.onRightDrawableClicked {
it.text.clear()
}
Its very simple.
Lets say you have a drawable on left side of your EditText 'txtsearch'.
Following will do the trick.
EditText txtsearch = (EditText) findViewById(R.id.txtsearch);
txtsearch.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= txtsearch.getTotalPaddingLeft()) {
// your action for drawable click event
return true;
}
}
return false;
}
});
If you want for right drawable change the if statement to:
if(event.getRawX() >= txtsearch.getRight() - txtsearch.getTotalPaddingRight())
Similarly, you can do it for all compound drawables.
txtsearch.getTotalPaddingTop()
txtsearch.getTotalPaddingBottom()
This method call returns all the padding on that side including any drawables. You can use this even for TextView, Button etc.
Click here for reference from android developer site.
I think it is much more easier if we use some tricks :)
Create a image button with your icon and set its background
color to be transparent.
Put the image button on the EditText and of coz the right hand side
Implement the onclick listener of the button to execute your
function
Done
That last contribution's use of contains(x,y) won't work directly on the result of getBounds() (except, by coincidence, when using "left" drawables). The getBounds method only provides the Rect defining points of the drawable item normalized with origin at 0,0 - so, you actually need to do the math of the original post to find out if the click is in the area of the drawable in the context of the containing EditText's dimensions, but change it for top, right, left etc. Alternatively you could describe a Rect that has coordinates actually relative to its position in the EditText container and use contains(), although in the end you're doing the same math.
Combining them both gives you a pretty complete solution, I only added an instance attribute consumesEvent that lets the API user decide if the click event should be passed on or not by using its result to set ACTION_CANCEL or not.
Also, I can't see why the bounds and actionX, actionY values are instance attributes rather than just local on the stack.
Here's a cutout from an implementation based on the above that I put together. It fixes an issue that to properly consume the event you need to return false. It adds a "fuzz" factor to. In my use case of a Voice control icon in an EditText field, I found it hard to click, so the fuzz increases the effective bounds that are considered clicking the drawable. For me 15 worked well. I only needed drawableRight so I didn't plug the math in the others, to save some space, but you see the idea.
package com.example.android;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.EditText;
import android.graphics.Rect;
import com.example.android.DrawableClickListener;
public class ClickableButtonEditText extends EditText {
public static final String LOG_TAG = "ClickableButtonEditText";
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
private boolean consumeEvent = false;
private int fuzz = 0;
private DrawableClickListener clickListener;
public ClickableButtonEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ClickableButtonEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClickableButtonEditText(Context context) {
super(context);
}
public void consumeEvent() {
this.setConsumeEvent(true);
}
public void setConsumeEvent(boolean b) {
this.consumeEvent = b;
}
public void setFuzz(int z) {
this.fuzz = z;
}
public int getFuzz() {
return fuzz;
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
if (right != null) {
drawableRight = right;
}
if (left != null) {
drawableLeft = left;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int x, y;
Rect bounds;
x = (int) event.getX();
y = (int) event.getY();
// this works for left since container shares 0,0 origin with bounds
if (drawableLeft != null) {
bounds = drawableLeft.getBounds();
if (bounds.contains(x - fuzz, y - fuzz)) {
clickListener.onClick(DrawableClickListener.DrawablePosition.LEFT);
if (consumeEvent) {
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
}
} else if (drawableRight != null) {
bounds = drawableRight.getBounds();
if (x >= (this.getRight() - bounds.width() - fuzz) && x <= (this.getRight() - this.getPaddingRight() + fuzz)
&& y >= (this.getPaddingTop() - fuzz) && y <= (this.getHeight() - this.getPaddingBottom()) + fuzz) {
clickListener.onClick(DrawableClickListener.DrawablePosition.RIGHT);
if (consumeEvent) {
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
}
} else if (drawableTop != null) {
// not impl reader exercise :)
} else if (drawableBottom != null) {
// not impl reader exercise :)
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable {
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
super.finalize();
}
public void setDrawableClickListener(DrawableClickListener listener) {
this.clickListener = listener;
}
}
I have implemented in Kotlin
edPassword.setOnTouchListener { _, event ->
val DRAWABLE_RIGHT = 2
val DRAWABLE_LEFT = 0
val DRAWABLE_TOP = 1
val DRAWABLE_BOTTOM = 3
if (event.action == MotionEvent.ACTION_UP) {
if (event.rawX >= (edPassword.right - edPassword.compoundDrawables[DRAWABLE_RIGHT].bounds.width())) {
edPassword.setText("")
true
}
}
false
}
Extending on the idea by RyanM I have created a more flexible version, which supports all the drawable types (top, bottom, left, right). While the code below extends TextView, adapting it for an EditText is just a case of swapping "extends TextView" with "extends EditText". Instantiation the widget from XML is identical as in RyanM's example, bar the widget name.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
import com.example.DrawableClickListener.DrawablePosition;
public class ButtonTextView extends TextView {
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
private int actionX, actionY;
private DrawableClickListener clickListener;
public ButtonTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ButtonTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ButtonTextView(Context context) {
super(context);
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
if (right != null) {
drawableRight = right;
}
if (left != null) {
drawableLeft = left;
}
if (top != null) {
drawableTop = top;
}
if (bottom != null) {
drawableBottom = bottom;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
actionX = (int) event.getX();
actionY = (int) event.getY();
if (drawableBottom != null && drawableBottom.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.BOTTOM);
return super.onTouchEvent(event);
}
if (drawableTop != null && drawableTop.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.TOP);
return super.onTouchEvent(event);
}
if (drawableLeft != null && drawableLeft.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.LEFT);
return super.onTouchEvent(event);
}
if (drawableRight != null && drawableRight.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.RIGHT);
return super.onTouchEvent(event);
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable {
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
super.finalize();
}
public void setDrawableClickListener(DrawableClickListener listener) {
this.clickListener = listener;
}}
The DrawableClickListener is as simple as this:
public interface DrawableClickListener {
public static enum DrawablePosition { TOP, BOTTOM, LEFT, RIGHT };
public void onClick(DrawablePosition target); }
And then the actual implementation:
class example implements DrawableClickListener {
public void onClick(DrawablePosition target) {
switch (target) {
case LEFT:
doSomethingA();
break;
case RIGHT:
doSomethingB();
break;
case BOTTOM:
doSomethingC();
break;
case TOP:
doSomethingD();
break;
default:
break;
}
}}
p.s.: If you don't set the listener, touching the TextView will cause a NullPointerException. You may want to add some more paranoia into the code.
its working for me,
mEditTextSearch.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(s.length()>0){
mEditTextSearch.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(android.R.drawable.ic_delete), null);
}else{
mEditTextSearch.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(R.drawable.abc_ic_search), null);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
mEditTextSearch.setOnTouchListener(new OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(mEditTextSearch.getCompoundDrawables()[2]!=null){
if(event.getX() >= (mEditTextSearch.getRight()- mEditTextSearch.getLeft() - mEditTextSearch.getCompoundDrawables()[2].getBounds().width())) {
mEditTextSearch.setText("");
}
}
}
return false;
}
});
I know this is quite old, but I recently had to do something similar... After seeing how difficult this is, I came up with a much simpler solution:
Create an XML layout that contains the EditText and Image
Subclass FrameLayout and inflate the XML layout
Add code for the click listener and any other behavior you want
In my case, I needed an EditText that had the ability to clear the text with a button. I wanted it to look like SearchView, but for a number of reasons I didn't want to use that class. The example below shows how I accomplished this. Even though it doesn't have to do with focus change, the principles are the same and I figured it would be more beneficial to post actual working code than to put together an example that may not work exactly as I intended:
Here is my layout: clearable_edit_text.xml
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/edit_text_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!-- NOTE: Visibility cannot be set to "gone" or the padding won't get set properly in code -->
<ImageButton
android:id="#+id/edit_text_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:background="#drawable/ic_cancel_x"
android:visibility="invisible"/>
</merge>
And here is the Class that inflates that layout: ClearableEditText.java
public class ClearableEditText extends FrameLayout {
private boolean mPaddingSet = false;
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
*/
public ClearableEditText (final Context context) {
this(context, null, 0);
}
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
* #param attrs The attribute set used to customize this instance
*/
public ClearableEditText (final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
* #param attrs The attribute set used to customize this instance
* #param defStyle The default style to be applied to this instance
*/
public ClearableEditText (final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.clearable_edit_text, this, true);
}
#Override
protected void onFinishInflate () {
super.onFinishInflate();
final EditText editField = (EditText) findViewById(R.id.edit_text_field);
final ImageButton clearButton = (ImageButton) findViewById(R.id.edit_text_clear);
//Set text listener so we can show/hide the close button based on whether or not it has text
editField.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged (final CharSequence charSequence, final int i, final int i2, final int i3) {
//Do nothing here
}
#Override
public void onTextChanged (final CharSequence charSequence, final int i, final int i2, final int i3) {
//Do nothing here
}
#Override
public void afterTextChanged (final Editable editable) {
clearButton.setVisibility(editable.length() > 0 ? View.VISIBLE : View.INVISIBLE);
}
});
//Set the click listener for the button to clear the text. The act of clearing the text will hide this button because of the
//text listener
clearButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick (final View view) {
editField.setText("");
}
});
}
#Override
protected void onLayout (final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
//Set padding here in the code so the text doesn't run into the close button. This could be done in the XML layout, but then if
//the size of the image changes then we constantly need to tweak the padding when the image changes. This way it happens automatically
if (!mPaddingSet) {
final EditText editField = (EditText) findViewById(R.id.edit_text_field);
final ImageButton clearButton = (ImageButton) findViewById(R.id.edit_text_clear);
editField.setPadding(editField.getPaddingLeft(), editField.getPaddingTop(), clearButton.getWidth(), editField.getPaddingBottom());
mPaddingSet = true;
}
}
}
To make this answer more in line with the question the following steps should be taken:
Change the drawable resource to whatever you want... In my case it was a gray X
Add a focus change listener to the edit text...
Simply copy paste the following code and it does the trick.
editMsg.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (editMsg.getRight() - editMsg.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here
Toast.makeText(ChatActivity.this, "Message Sent", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
None of the previous solutions worked for me in Xamarin Android. I was able to get the right drawable click listener working using the following:
Create the following OnEditTextTouch event listener:
private void OnEditTextTouch(object sender, View.TouchEventArgs e)
{
var rightDrawable = _autoCompleteTextViewSearch.GetCompoundDrawables()[2];
if (rightDrawable == null || e.Event.Action != MotionEventActions.Up)
{
e.Handled = false;
return;
}
if (e.Event.GetX() >= _autoCompleteTextViewSearch.Width - _autoCompleteTextViewSearch.TotalPaddingRight)
{
// Invoke your desired action here.
e.Handled = true;
}
// Forward the event along to the sender (crucial for default behaviour)
(sender as AutoCompleteTextView)?.OnTouchEvent(e.Event);
}
Subscribe to the Touch event:
_autoCompleteTextViewSearch.Touch += OnEditTextTouch;
I've taked the solution of #AZ_ and converted it in a kotlin extension function:
So copy this in your code:
#SuppressLint("ClickableViewAccessibility")
fun EditText.setDrawableRightTouch(setClickListener: () -> Unit) {
this.setOnTouchListener(View.OnTouchListener { _, event ->
val DRAWABLE_LEFT = 0
val DRAWABLE_TOP = 1
val DRAWABLE_RIGHT = 2
val DRAWABLE_BOTTOM = 3
if (event.action == MotionEvent.ACTION_UP) {
if (event.rawX >= this.right - this.compoundDrawables[DRAWABLE_RIGHT].bounds.width()
) {
setClickListener()
return#OnTouchListener true
}
}
false
})
}
You can use it just calling the setDrawableRightTouch function on your EditText:
yourEditText.setDrawableRightTouch {
//your code
}
A probable solution to the above problem could be using android's new material component TextInputLayout.
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/searchInput"
style="#style/Widget.App.TextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/search"
app:endIconMode="custom"
app:endIconContentDescription="Search"
app:endIconDrawable="#drawable/ic_search">
<EditText
android:id="#+id/et_search"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.google.android.material.textfield.TextInputLayout>
Here the TextInputLayout attribute endIconMode when set, places a button at the end of the enclosed EditText.
Additionally app:endIconMode = "custom" allows customization of the icon's click functonality
Finally to listen to the end icon clicks call setEndIconClickListener() on the enclosing TextInputLayout component.
#Override
public boolean onTouch(View v, MotionEvent event) {
Drawable drawableObj = getResources().getDrawable(R.drawable.search_btn);
int drawableWidth = drawableObj.getIntrinsicWidth();
int x = (int) event.getX();
int y = (int) event.getY();
if (event != null && event.getAction() == MotionEvent.ACTION_UP) {
if (x >= (searchPanel_search.getWidth() - drawableWidth - searchPanel_search.getPaddingRight())
&& x <= (searchPanel_search.getWidth() - searchPanel_search.getPaddingRight())
&& y >= searchPanel_search.getPaddingTop() && y <= (searchPanel_search.getHeight() - searchPanel_search.getPaddingBottom())) {
getSearchData();
}
else {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchPanel_search, InputMethodManager.SHOW_FORCED);
}
}
return super.onTouchEvent(event);
}
and if drawable is on the left, this will help you. (for those work with RTL layout)
editComment.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() <= (searchbox.getLeft() + searchbox.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width())) {
// your action here
return true;
}
}
return false;
}
});
It is all great but why not to make it really simple?
I have faced with that also not so long ago...and android touchlistiner works great but gives limitation in usage..and I came to another solution and I hope that will help you:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/zero_row">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ProgressBar
android:id="#+id/loadingProgressBar"
android:layout_gravity="center"
android:layout_width="28dp"
android:layout_height="28dp" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:background="#drawable/edittext_round_corners"
android:layout_height="match_parent"
android:layout_marginLeft="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#android:drawable/ic_menu_search"
android:id="#+id/imageView2"
android:layout_weight="0.15"
android:layout_gravity="center|right"
android:onClick="OnDatabaseSearchEvent" />
<EditText
android:minHeight="40dp"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/edittext_round_corners"
android:inputType="textPersonName"
android:hint="Search.."
android:textColorHint="#color/AndroidWhite"
android:textColor="#color/AndroidWhite"
android:ems="10"
android:id="#+id/e_d_search"
android:textCursorDrawable="#color/AndroidWhite"
android:layout_weight="1" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#drawable/ic_oculi_remove2"
android:id="#+id/imageView3"
android:layout_gravity="center|left"
android:layout_weight="0.15"
android:onClick="onSearchEditTextCancel" />
</LinearLayout>
<!--android:drawableLeft="#android:drawable/ic_menu_search"-->
<!--android:drawableRight="#drawable/ic_oculi_remove2"-->
</LinearLayout>
</LinearLayout>
Now you can create ImageClick listener or event and do what ever you want with text. This edittext_round_corners.xml file
<item android:state_pressed="false" android:state_focused="false">
<shape>
<gradient
android:centerY="0.2"
android:startColor="#color/colorAccent"
android:centerColor="#color/colorAccent"
android:endColor="#color/colorAccent"
android:angle="270"
/>
<stroke
android:width="0.7dp"
android:color="#color/colorAccent" />
<corners
android:radius="5dp" />
</shape>
</item>
Better to have ImageButton on Right of edit text and give negative layout margin to overlap with edit text. Set listener on ImageButton and perform operations.
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp" >
<EditText
android:id="#+id/edt_status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="#drawable/txt_box_blank"
android:ems="10"
android:hint="#string/statusnote"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:textColor="#android:color/black" />
<Button
android:id="#+id/note_del"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="1dp"
android:layout_marginTop="5dp"
android:background="#android:drawable/ic_delete" />
</FrameLayout>
Compound drawables are not supposed to be clickable.
It is cleaner to use separate views in a horizontal LinearLayout and use a click handler on them.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="#color/white"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:translationZ="4dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/white"
android:minWidth="40dp"
android:scaleType="center"
app:srcCompat="#drawable/ic_search_map"/>
<android.support.design.widget.TextInputEditText
android:id="#+id/search_edit"
style="#style/EditText.Registration.Map"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="#string/hint_location_search"
android:imeOptions="actionSearch"
android:inputType="textPostalAddress"
android:maxLines="1"
android:minHeight="40dp" />
<ImageView
android:id="#+id/location_gps_refresh"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/white"
android:minWidth="40dp"
android:scaleType="center"
app:srcCompat="#drawable/selector_ic_gps"/>
</LinearLayout>
For anyone who does not want to implement the monstrous click handling. You can achieve the same with a RelativeLayout. With that you even have free handling of the positioning of the drawable.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</android.support.design.widget.TextInputLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:src="#drawable/ic_undo"/>
</RelativeLayout>
The ImageView position will be the same as you would use drawableEnd - plus you don't need all the touch listener handling. Just a click listener for the ImageView and you are good to go.
This works fro me:) may this help you as well
edit_account_name.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getRawX() >= (edit_account_name.getRight())) {
//clicked
return true;
}
}
return false;
}
});
I've seen several solutions but I wasn't convinced by any of them. Either very complicated or too simple (non-reusable).
This is my favourite approach at the moment:
mEditText.setOnTouchListener(
new OnEditTextRightDrawableTouchListener(mEditText) {
#Override
public void OnDrawableClick() {
// The right drawable was clicked. Your action goes here.
}
});
And this is the reusable touch listener:
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.EditText;
public abstract class OnEditTextRightDrawableTouchListener implements OnTouchListener {
private final EditText mEditText;
public OnEditTextRightDrawableTouchListener(#NonNull final EditText editText) {
mEditText = editText;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
final int DRAWABLE_RIGHT_POSITION = 2;
final Drawable drawable = mEditText.getCompoundDrawables()[DRAWABLE_RIGHT_POSITION];
if (drawable != null) {
final float touchEventX = motionEvent.getX();
final int touchAreaRight = mEditText.getRight();
final int touchAreaLeft = touchAreaRight - drawable.getBounds().width();
if (touchEventX >= touchAreaLeft && touchEventX <= touchAreaRight) {
view.performClick();
OnDrawableClick();
}
return true;
}
}
return false;
}
public abstract void OnDrawableClick();
}
You can look at the Gist here.
Follow below code for drawable right,left,up,down click:
edittextview_confirmpassword.setOnTouchListener(new View.OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (edittextview_confirmpassword.getRight() - edittextview_confirmpassword.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here edittextview_confirmpassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
return true;
}
}else{
edittextview_confirmpassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
return false;
}
});
}
Here's my simple solution, just place ImageButton over EditText:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:id="#+id/editTextName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="text"/>
<ImageButton android:id="#+id/imageViewSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_action_search"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
for left drawable click listener
txt.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
if (event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() <= (txt
.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width() +
txt.getPaddingLeft() +
txt.getLeft())) {
//TODO do code here
}
return true;
}
}
return false;
}
});
I would like to suggest a way for drawable left!
I tried this code and works.
txtsearch.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
int start=txtsearch.getSelectionStart();
int end=txtsearch.getSelectionEnd();
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= (txtsearch.getLeft() + txtsearch.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width())) {
//Do your action here
return true;
}
}
return false;
}
});
}
I implemented #aristo_sh answer in Mono.Droid (Xamarin), since it's a delegate anonymous method you can't return true or false you have to take take of e.Event.Handled. I am also hiding the keyboard on click
editText.Touch += (sender, e) => {
e.Handled = false;
if (e.Event.Action == MotionEventActions.Up)
{
if (e.Event.RawX >= (bibEditText.Right - (bibEditText.GetCompoundDrawables()[2]).Bounds.Width()))
{
SearchRunner();
InputMethodManager manager = (InputMethodManager)GetSystemService(InputMethodService);
manager.HideSoftInputFromWindow(editText.WindowToken, 0);
e.Handled = true;
}
}
};
Related
Currently I have a Button in a layout, however an assigned OnClickListener never calls back to the onClick method.
Is it possible to intercept the click of a Button in a layout assigned to a MarkerView?
I have finished my app with clickable marker view. My solution is that we'll create a subclass of LineChart (or other chart), then let override onTouchEvent and detect the touch location.
public class MyChart extends LineChart {
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = true;
// if there is no marker view or drawing marker is disabled
if (isShowingMarker() && this.getMarker() instanceof ChartInfoMarkerView){
ChartInfoMarkerView markerView = (ChartInfoMarkerView) this.getMarker();
Rect rect = new Rect((int)markerView.drawingPosX,(int)markerView.drawingPosY,(int)markerView.drawingPosX + markerView.getWidth(), (int)markerView.drawingPosY + markerView.getHeight());
if (rect.contains((int) event.getX(),(int) event.getY())) {
// touch on marker -> dispatch touch event in to marker
markerView.dispatchTouchEvent(event);
}else{
handled = super.onTouchEvent(event);
}
}else{
handled = super.onTouchEvent(event);
}
return handled;
}
private boolean isShowingMarker(){
return mMarker != null && isDrawMarkersEnabled() && valuesToHighlight();
}
}
public class ChartInfoMarkerView extends MarkerView {
#BindView(R.id.markerContainerView)
LinearLayout markerContainerView;
protected float drawingPosX;
protected float drawingPosY;
private static final int MAX_CLICK_DURATION = 500;
private long startClickTime;
/**
* The constructor
*
* #param context
* #param layoutResource
*/
public ChartInfoMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
ButterKnife.bind(this);
markerContainerView.setClickable(true);
markerContainerView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("MARKER","click");
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
startClickTime = Calendar.getInstance().getTimeInMillis();
break;
}
case MotionEvent.ACTION_UP: {
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
if(clickDuration < MAX_CLICK_DURATION) {
markerContainerView.performClick();
}
}
}
return super.onTouchEvent(event);
}
#Override
public void draw(Canvas canvas, float posX, float posY) {
super.draw(canvas, posX, posY);
MPPointF offset = getOffsetForDrawingAtPoint(posX, posY);
this.drawingPosX = posX + offset.x;
this.drawingPosY = posY + offset.y;
}
}
Using the library it appears not to be possible, however a solution of sorts is to show a View or ViewGroup over the chart which has a Button in it. You’ll need to set up an empty layout for the MarkerView and wrap your Chart in a ViewGroup such as a RelativeLayout.
Define a listener such as this in your CustomMarkerView:
public interface Listener {
/**
* A callback with the x,y position of the marker
* #param x the x in pixels
* #param y the y in pixels
*/
void onMarkerViewLayout(int x, int y);
}
Set up some member variables:
private Listener mListener;
private int mLayoutX;
private int mLayoutY;
private int mMarkerVisibility;
In your constructor require a listener:
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
* #param context a context
* #param layoutResource the layout resource to use for the MarkerView
* #param listener listens for the bid now click
*/
public SQUChartMarkerView(Context context, int layoutResource, Listener listener) {
super(context, layoutResource);
mListener = listener;
}
Store the location the marker should be when the values are set:
#Override public int getXOffset(float xpos) {
mLayoutX = (int) (xpos - (getWidth() / 2));
return -getWidth() / 2;
}
#Override public int getYOffset(float ypos) {
mLayoutY = (int) (ypos - getWidth());
return -getHeight();
}
Then override onDraw to determine when you should draw your layout:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mMarkerVisibility == View.VISIBLE) mListener.onMarkerViewLayout(mLayoutX, mLayoutY);
}
I added an option to change the state of the marker:
public void setMarkerVisibility(int markerVisibility) {
mMarkerVisibility = markerVisibility;
}
Where you listen for marker being laid out, get your layout (eg. inflate it or you may have it as a member variable), make sure you measure it, then set the margins. In this case I am using getParent() as the chart and the layout for the marker share the same parent. I have a BarChart so my margins may be different from yours.
#Override public void onMarkerViewLayout(int x, int y) {
if(getParent() == null || mChartListener.getAmount() == null) return;
// remove the marker
((ViewGroup) getParent()).removeView(mMarkerLayout);
((ViewGroup) getParent()).addView(mMarkerLayout);
// measure the layout
// if this is not done, the first calculation of the layout parameter margins
// will be incorrect as the layout at this point has no height or width
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(((ViewGroup) getParent()).getWidth(), View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(((ViewGroup) getParent()).getHeight(), View.MeasureSpec.UNSPECIFIED);
mMarkerLayout.measure(widthMeasureSpec, heightMeasureSpec);
// set up layout parameters so our marker is in the same position as the mpchart marker would be (based no the x and y)
RelativeLayout.LayoutParams lps = (RelativeLayout.LayoutParams) mMarkerLayout.getLayoutParams();
lps.height = FrameLayout.LayoutParams.WRAP_CONTENT;
lps.width = FrameLayout.LayoutParams.WRAP_CONTENT;
lps.leftMargin = x - mMarkerLayout.getMeasuredWidth() / 2;
lps.topMargin = y - mMarkerLayout.getMeasuredHeight();
}
Hope this helps.
I want to slowdown SlidingDrawer opening speed using interpolator (DecelerateInterpolator)
Is this possible. i want to implement ease animation.
final Animation slowDown = AnimationUtils.loadAnimation(((Swipe)getActivity()), R.anim.ease);
this is XML
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/decelerate_interpolator">
<translate
android:fromYDelta="0"
android:toYDelta="100%p"
android:duration="2000"/>
</set>
Using this i am not getting what i want.
Actually, if You want only open/close that sliding layout, than I would suggest to use custom ViewGroup instead of deprecated SlidingDrawer. Below is some simple example of that implementation:
public class MySimpleSlidingDrawer extends RelativeLayout implements View.OnClickListener {
private static final int SLIDING_TIME = 500;
private View mHandle;
private int mHandleId;
private View mContent;
private int mContentId;
private ObjectAnimator mOpenAnimator = ObjectAnimator.ofFloat(this, "slide", 0f, 1f);
private ObjectAnimator mCloseAnimator = ObjectAnimator.ofFloat(this, "slide", 1f, 0f);
private int mAnimationTime = SLIDING_TIME;
private boolean mOpened = false;
private int mSlideHeight = 0;
public MySimpleSlidingDrawer(final Context context) {
super(context);
init(context, null, 0);
}
public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(final Context context, final AttributeSet attrs, final int defStyle) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingDrawer, defStyle, 0);
mHandleId = a.getResourceId(R.styleable.MySlidingDrawer_handle, 0);
mContentId = a.getResourceId(R.styleable.MySlidingDrawer_content, 0);
mOpenAnimator.setInterpolator(new AccelerateInterpolator());
mOpenAnimator.setDuration(SLIDING_TIME);
mCloseAnimator.setInterpolator(new DecelerateInterpolator());
mCloseAnimator.setDuration(SLIDING_TIME);
setClipChildren(false);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() != 2) {
throw new InflateException("Only to child are supported for this layout");
}
if (mHandleId != 0) {
mHandle = findViewById(mHandleId);
} else {
mHandle = getChildAt(0);
}
if (mContentId != 0) {
mContent = findViewById(mContentId);
} else {
mContent = getChildAt(1);
}
final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
handleParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
handleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
handleParams.addRule(ALIGN_PARENT_BOTTOM, 1/* true */);
handleParams.addRule(CENTER_HORIZONTAL, 1/* true */);
mHandle.setLayoutParams(handleParams);
mHandle.setOnClickListener(this);
}
#Override
public void onClick(final View v) {
if (mSlideHeight == 0) {
mSlideHeight = getHeight() - mHandle.getHeight();
}
// Handle have been clicked. Execute animation depending on open / close state
if (!mOpened) {
mOpened = true;
mCloseAnimator.cancel();
mOpenAnimator.start();
} else {
mOpened = false;
mOpenAnimator.cancel();
mCloseAnimator.start();
}
}
/**
* Sets slide percent value
*
* #param slidePercent % of slide (0 - closed, 1 - opened)
*/
#SuppressWarnings("UnusedDeclaration")
public void setSlide(final float slidePercent) {
final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
handleParams.bottomMargin = (int) (slidePercent * mSlideHeight);
mHandle.setLayoutParams(handleParams);
final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
contentParams.bottomMargin = (int) -((1- slidePercent) * mSlideHeight);
mContent.setLayoutParams(contentParams);
}
/**
* Sets open interpolator
*
* #param interpolator {#link android.view.animation.Interpolator} for open animation
*/
#SuppressWarnings("UnusedDeclaration")
public void setOpenInterpolator(final Interpolator interpolator) {
if (mOpenAnimator.isRunning()) {
mOpenAnimator.cancel();
}
mOpenAnimator.setInterpolator(interpolator);
}
/**
* Sets close interpolator
*
* #param interpolator {#link android.view.animation.Interpolator} for close animation
*/
#SuppressWarnings("UnusedDeclaration")
public void setCloseInterpolator(final Interpolator interpolator) {
if (mCloseAnimator.isRunning()) {
mCloseAnimator.cancel();
}
mCloseAnimator.setInterpolator(interpolator);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Trick to avoid content to be resized - measure it as it visible
final int contentHeightMeasure = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - mHandle.getMeasuredHeight(), MeasureSpec.EXACTLY);
final int contentWidthMeasure = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
mContent.measure(contentHeightMeasure, contentWidthMeasure);
if (mSlideHeight == 0) {
mSlideHeight = getMeasuredHeight() - mHandle.getMeasuredHeight();
final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
contentParams.height = mSlideHeight;
contentParams.addRule(ALIGN_PARENT_BOTTOM, 1 /* true */);
contentParams.bottomMargin = - mSlideHeight;
mContent.setLayoutParams(contentParams);
}
}
}
NOTE: it's not optimized or tested a lot, but basic functionality works.
Another way is to adapt existing SlidingDrawer code to your need. For me it looks not so easy or flexible, because of its existing implementation specific. First of all, it clear mentioned in SlidingDrawer documentation:
This class is not supported anymore. It is recommended you base your
own implementation on the source code for the Android Open Source
Project if you must use it in your application.
And there's no animation change API exposed by SlidingDrawer. The main problem is that there's no animation at all, some timing event is just sent to update view position, here:
private void doAnimation() {
if (mAnimating) {
incrementAnimation();
if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
mAnimating = false;
closeDrawer();
} else if (mAnimationPosition < mTopOffset) {
mAnimating = false;
openDrawer();
} else {
moveHandle((int) mAnimationPosition);
mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
mCurrentAnimationTime);
}
}
}
So, in order to modify interpolation, you will need to change what animation logic or provide your own. Second way is safer, because doesn't affect existing logic. Below is some draft variant how to do it (to detect exact changes comparing with original SlidingDrawer open below class diff with original API 19 version from your android SDK installation), only changed code is presented below:
public class MySlidingDrawer extends ViewGroup {
/** Click animation duration */
// TODO: ideally you should properly calculate that value
private static final long CLICK_ANIMATION_DURATION = 1000;
/** New field for custom interpolator */
private TimeInterpolator mAnimationInterpolator = new BounceInterpolator();
/** just to distinguish click and moving by user animations */
private boolean mAnimatedClick = false;
/** Specific click animator */
private ObjectAnimator mClickToggleAnimation;
/** Specific listener just to handle animation end properly */
private Animator.AnimatorListener mClickAnimationListener = new Animator.AnimatorListener() {
#Override
public void onAnimationStart(final Animator animation) {
// nothing to do here
}
#Override
public void onAnimationEnd(final Animator animation) {
mAnimating = false;
// Determine if it close or open, by comparing to some final value
if (mAnimationPosition == mTopOffset) {
openDrawer();
} else {
closeDrawer();
}
}
#Override
public void onAnimationCancel(final Animator animation) {
// TODO: should be handled properly
}
#Override
public void onAnimationRepeat(final Animator animation) {
}
};
...
/**
* Creates a new SlidingDrawer from a specified set of attributes defined in XML.
*
* #param context The application's environment.
* #param attrs The attributes defined in XML.
*/
public MySlidingDrawer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
...
#Override
public boolean onTouchEvent(MotionEvent event) {
if (mLocked) {
return true;
}
if (mTracking) {
mVelocityTracker.addMovement(event);
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(mVelocityUnits);
float yVelocity = velocityTracker.getYVelocity();
float xVelocity = velocityTracker.getXVelocity();
boolean negative;
final boolean vertical = mVertical;
if (vertical) {
negative = yVelocity < 0;
if (xVelocity < 0) {
xVelocity = -xVelocity;
}
if (xVelocity > mMaximumMinorVelocity) {
xVelocity = mMaximumMinorVelocity;
}
} else {
negative = xVelocity < 0;
if (yVelocity < 0) {
yVelocity = -yVelocity;
}
if (yVelocity > mMaximumMinorVelocity) {
yVelocity = mMaximumMinorVelocity;
}
}
float velocity = (float) Math.hypot(xVelocity, yVelocity);
if (negative) {
velocity = -velocity;
}
final int top = mHandle.getTop();
final int left = mHandle.getLeft();
if (Math.abs(velocity) < mMaximumTapVelocity) {
if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
(!mExpanded && top > mBottomOffset + getBottom() - getTop() -
mHandleHeight - mTapThreshold) :
(mExpanded && left < mTapThreshold + mTopOffset) ||
(!mExpanded && left > mBottomOffset + getRight() - getLeft() -
mHandleWidth - mTapThreshold)) {
if (mAllowSingleTap) {
playSoundEffect(SoundEffectConstants.CLICK);
animateToggle();
/*
if (mExpanded) {
animateClose(vertical ? top : left);
} else {
animateOpen(vertical ? top : left);
}
*/
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
}
break;
}
}
return mTracking || mAnimating || super.onTouchEvent(event);
}
...
/**
* Toggles the drawer open and close with an animation.
*
* #see #open()
* #see #close()
* #see #animateClose()
* #see #animateOpen()
* #see #toggle()
*/
public void animateToggle() {
mAnimatedClick = true;
if (!mExpanded) {
animateClickOpen();
} else {
animateClickClose();
}
}
/**
* For doing our animation for close
*/
private void animateClickClose() {
mAnimating = true;
mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int) mAnimationPosition, mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1);
mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
mClickToggleAnimation.addListener(mClickAnimationListener);
mClickToggleAnimation.start();
}
/**
* For doing our animation for open
*/
private void animateClickOpen() {
mAnimating = true;
mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int)mAnimationPosition, mTopOffset);
mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
mClickToggleAnimation.addListener(mClickAnimationListener);
mClickToggleAnimation.start();
}
/**
* Sets current animation position
*
* #param position to be set
*/
#SuppressWarnings("UnusedDeclaration")
public void setTogglePosition(final int position) {
mAnimationPosition = position;
moveHandle((int) mAnimationPosition);
}
...
private class DrawerToggler implements OnClickListener {
public void onClick(View v) {
if (mLocked) {
return;
}
// mAllowSingleTap isn't relevant here; you're *always*
// allowed to open/close the drawer by clicking with the
// trackball.
if (mAnimateOnClick) {
animateToggle();
} else {
toggle();
}
}
}
...
/**
* New API to modify timing of interpolator
*
* #param interpolator {#link android.animation.TimeInterpolator} to be used for onClick open / close
*
* TODO: it's also possible to add XML attribute for the same
*/
public void setAnimationInterpolator(final TimeInterpolator interpolator) {
mAnimationInterpolator = interpolator;
}
}
And there's xml I've used for testing (only one sliding drawer section should be enabled):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.alexstarc.testapp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Some fragment with main content of activity screen -->
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.alexstarc.testapp.VoteListFragment"
android:tag="mainFragment"/>
<com.alexstarc.testapp.MySlidingDrawer
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:handle="#+id/handle"
custom:orientation="vertical"
custom:content="#+id/content">
<ImageView
android:id="#id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/ic_delete"/>
<ImageView
android:id="#id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/cover"
android:scaleType="fitXY"/>
</com.alexstarc.testapp.MySlidingDrawer>
<!--
<com.alexstarc.testapp.MySimpleSlidingDrawer
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:handle="#+id/handle"
custom:content="#+id/content">
<ImageView
android:id="#id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/ic_delete"/>
<ImageView
android:id="#id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/cover"
android:scaleType="fitXY"/>
</com.alexstarc.testapp.MySimpleSlidingDrawer>
-->
</RelativeLayout>
I have tried several solutions but need help. The topics below are really useful but I think I'm doing something wrong. How to set layout height/settings for both? Let's say I have 2 LinearLayout for content and bottom menu.
Also I don't want the bottom menu disappeared after sliding. It should be constant there. I am using fragments for menu clicks/change views.
Android: Expand/collapse animation
Android animate drop down/up view proper
As my comment seemed to help, I will post the link as an answer: https://github.com/umano/AndroidSlidingUpPanel
The full code cannot be pasted in StackOverflow, but the whole library will help you to achieve what you need.
The 2.2 version of the Umano Android app features a sexy sliding up
draggable panel for the currently playing article. This type of a
panel is a common pattern also used in the Google Music app and the
Rdio app. This is an open source implementation of this component that
you are free to take advantage of in your apps. Umano Team <3 Open
Source.
<com.sothree.slidinguppaneldemo.SlidingUpPanelLayout
android:id="#+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Main Content"
android:textSize="16sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|top"
android:text="The Awesome Sliding Up Panel"
android:textSize="16sp" />
</com.sothree.slidinguppaneldemo.SlidingUpPanelLayout>
You can also try this custom view for ExpandablePanel found it somewhere when i needed to create something like this.
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
public class ExpandablePanel extends LinearLayout {
private final int mHandleId;
private final int mContentId;
// Contains references to the handle and content views
private View mHandle;
private View mContent;
// Does the panel start expanded?
private boolean mExpanded = false;
// The height of the content when collapsed
private int mCollapsedHeight = 0;
// The full expanded height of the content (calculated)
private int mContentHeight = 0;
// How long the expand animation takes
private int mAnimationDuration = 0;
int height;
private Context context;
// Listener that gets fired onExpand and onCollapse
private OnExpandListener mListener;
public ExpandablePanel(Context context) {
this(context, null);
this.context = context;
}
public void setSize(int size) {
this.height = size;
}
/**
* The constructor simply validates the arguments being passed in and sets
* the global variables accordingly. Required attributes are 'handle' and
* 'content'
*/
public ExpandablePanel(Context context, AttributeSet attrs) {
super(context, attrs);
mListener = new DefaultOnExpandListener();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ExpandablePanel, 0, 0);
// How high the content should be in "collapsed" state
mCollapsedHeight = (int) a.getDimension(
R.styleable.ExpandablePanel_collapsedHeight, 0.0f);
// How long the animation should take
mAnimationDuration = a.getInteger(
R.styleable.ExpandablePanel_animationDuration, 500);
int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
if (handleId == 0) {
throw new IllegalArgumentException(
"The handle attribute is required and must refer "
+ "to a valid child.");
}
int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
if (contentId == 0) {
throw new IllegalArgumentException(
"The content attribute is required and must "
+ "refer to a valid child.");
}
mHandleId = handleId;
mContentId = contentId;
a.recycle();
}
// Some public setters for manipulating the
// ExpandablePanel programmatically
public void setOnExpandListener(OnExpandListener listener) {
mListener = listener;
}
public void setCollapsedHeight(int collapsedHeight) {
mCollapsedHeight = collapsedHeight;
}
public void setAnimationDuration(int animationDuration) {
mAnimationDuration = animationDuration;
}
/**
* This method gets called when the View is physically visible to the user
*/
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mHandle = findViewById(mHandleId);
if (mHandle == null) {
throw new IllegalArgumentException(
"The handle attribute is must refer to an"
+ " existing child.");
}
mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException(
"The content attribute must refer to an"
+ " existing child.");
}
// This changes the height of the content such that it
// starts off collapsed
android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
lp.height = mCollapsedHeight;
mContent.setLayoutParams(lp);
// Set the OnClickListener of the handle view
mHandle.setOnClickListener(new PanelToggler());
}
/**
* This is where the magic happens for measuring the actual (un-expanded)
* height of the content. If the actual height is less than the
* collapsedHeight, the handle will be hidden.
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// First, measure how high content wants to be
mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
mContentHeight = mContent.getMeasuredHeight();
Log.v("cHeight", mContentHeight + "");
Log.v("cCollapseHeight", mCollapsedHeight + "");
if (mContentHeight < mCollapsedHeight) {
mHandle.setVisibility(View.GONE);
} else {
mHandle.setVisibility(View.VISIBLE);
}
// Then let the usual thing happen
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* This is the on click listener for the handle. It basically just creates a
* new animation instance and fires animation.
*/
private class PanelToggler implements OnClickListener {
public void onClick(View v) {
Animation a;
if (mExpanded) {
a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
mListener.onCollapse(mHandle, mContent);
} else {
a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
mListener.onExpand(mHandle, mContent);
}
a.setDuration(mAnimationDuration);
mContent.startAnimation(a);
mExpanded = !mExpanded;
}
}
/**
* This is a private animation class that handles the expand/collapse
* animations. It uses the animationDuration attribute for the length of
* time it takes.
*/
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
/**
* Simple OnExpandListener interface
*/
public interface OnExpandListener {
public void onExpand(View handle, View content);
public void onCollapse(View handle, View content);
}
private class DefaultOnExpandListener implements OnExpandListener {
public void onCollapse(View handle, View content) {
}
public void onExpand(View handle, View content) {
}
}
}
Hi I am using the Gallery widget to show images downloaded from the internet.
to show several images and I would like to have a gradual zoom while people slide up and down on the screen. I know how to implement the touch event the only thing I don't know how to make the whole gallery view grow gradually. I don't want to zoom in on one image I want the whole gallery to zoom in/out gradually.
EDIT3: I manage to zoom the visible part of the gallery but the problem is I need to find a way for the gallery to find out about it and update it's other children too.
What happens is if 3 images are visible then you start zooming and the gallery does get smaller, so do the images but what I would like in this case is more images to be visible but I don't know how to reach this desired effect. Here's the entire code:
public class Gallery1 extends Activity implements OnTouchListener {
private static final String TAG = "GalleryTest";
private float zoom=0.0f;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
Gallery g;
LinearLayout layout2;
private ImageAdapter ad;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_1);
layout2=(LinearLayout) findViewById(R.id.layout2);
// Reference the Gallery view
g = (Gallery) findViewById(R.id.gallery);
// Set the adapter to our custom adapter (below)
ad=new ImageAdapter(this);
g.setAdapter(ad);
layout2.setOnTouchListener(this);
}
public void zoomList(boolean increase) {
Log.i(TAG, "startig animation");
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(g, "scaleX", zoom),
ObjectAnimator.ofFloat(g, "scaleY", zoom)
);
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
}
});
set.setDuration(100).start();
}
public class ImageAdapter extends BaseAdapter {
private static final int ITEM_WIDTH = 136;
private static final int ITEM_HEIGHT = 88;
private final int mGalleryItemBackground;
private final Context mContext;
private final Integer[] mImageIds = {
R.drawable.gallery_photo_1,
R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3,
R.drawable.gallery_photo_4,
R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6,
R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8
};
private final float mDensity;
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
// Gallery1.
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 1);
a.recycle();
mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
convertView = new ImageView(mContext);
imageView = (ImageView) convertView;
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new Gallery.LayoutParams(
(int) (ITEM_WIDTH * mDensity + 0.5f),
(int) (ITEM_HEIGHT * mDensity + 0.5f)));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mImageIds[position]);
return imageView;
}
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& event.getPointerCount() > 1) {
midPoint(mid, event);
if(mid.y > start.y){
Log.i(TAG, "Going down (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); // going down so increase
if ((Math.abs(mid.y - start.y) > 10) && (zoom<2.5f)){
zoom=zoom+0.1f;
midPoint(start, event);
zoomList(true);
}
return true;
}else if(mid.y < start.y){
Log.i(TAG, "Going up (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); //smaller
if ((Math.abs(mid.y - start.y) > 10) &&(zoom>0.1)){
midPoint(start, event);
zoom=zoom-0.1f;
zoomList(false);
}
return true;
}
}
else if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
Log.e(TAG, "Pointer went down: " + event.getPointerCount());
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "Pointer going up");
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Pointer going down");
start.set(event.getX(), event.getY());
return true;
}
return false;
// indicate event was handled or not
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
I realise I will probably have to extend the Gallery or even another View group or create my own class but I don't know where to start: which method use the one responsible for scaling...
EDIT4: I don't know if he question is clear enough. Here is an example of states:
State one: initial state, we have 3 images in view
State 2: we detect vertical touches going up with 2 fingers = we have to zoom out
state 3: we start zooming = animation on the gallery or on the children???
state 4: gallery detects that it's 3 children are smaller
state 5: gallery adds 1 /more children according to the new available space
LAST UPDATE:
Thanks to all that have posted but I have finally reached a conclusion and that is to not use Gallery at all:
1. It's deprecated
2. It's not customizable enough for my case
If you want to animate several images at once you may want to consider using OpenGl, I am using libgdx library:
https://github.com/libgdx/libgdx
The following ScalingGallery implementation might be of help.
This gallery subclass overrides the getChildStaticTransformation(View child, Transformation t) method in which the scaling is performed. You can further customize the scaling parameters to fit your own needs.
Please note the ScalingGalleryItemLayout.java class. This is necessary because after you have performed the scaling operationg on the child views, their hit boxes are no longer valid so they must be updated from with the getChildStaticTransformation(View child, Transformation t) method.
This is done by wrapping each gallery item in a ScalingGalleryItemLayout which extends a LinearLayout. Again, you can customize this to fit your own needs if a LinearLayout does not meet your needs for layout out your gallery items.
File : /src/com/example/ScalingGallery.java
/**
* A Customized Gallery component which alters the size and position of its items based on their position in the Gallery.
*/
public class ScalingGallery extends Gallery {
public static final int ITEM_SPACING = -20;
private static final float SIZE_SCALE_MULTIPLIER = 0.25f;
private static final float ALPHA_SCALE_MULTIPLIER = 0.5f;
private static final float X_OFFSET = 20.0f;
/**
* Implemented by child view to adjust the boundaries after it has been matrix transformed.
*/
public interface SetHitRectInterface {
public void setHitRect(RectF newRect);
}
/**
* #param context
* Context that this Gallery will be used in.
* #param attrs
* Attributes for this Gallery (via either xml or in-code)
*/
public ScalingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
setStaticTransformationsEnabled(true);
setChildrenDrawingOrderEnabled(true);
}
/**
* {#inheritDoc}
*
* #see #setStaticTransformationsEnabled(boolean)
*
* This is where the scaling happens.
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
child.invalidate();
t.clear();
t.setTransformationType(Transformation.TYPE_BOTH);
// Position of the child in the Gallery (... +2 +1 0 -1 -2 ... 0 being the middle)
final int childPosition = getSelectedItemPosition() - getPositionForView(child);
final int childPositionAbs = (int) Math.abs(childPosition);
final float left = child.getLeft();
final float top = child.getTop();
final float right = child.getRight();
final float bottom = child.getBottom();
Matrix matrix = t.getMatrix();
RectF modifiedHitBox = new RectF();
// Change alpha, scale and translate non-middle child views.
if (childPosition != 0) {
final int height = child.getMeasuredHeight();
final int width = child.getMeasuredWidth();
// Scale the size.
float scaledSize = 1.0f - (childPositionAbs * SIZE_SCALE_MULTIPLIER);
if (scaledSize < 0) {
scaledSize = 0;
}
matrix.setScale(scaledSize, scaledSize);
float moveX = 0;
float moveY = 0;
// Moving from right to left -- linear move since the scaling is done with respect to top-left corner of the view.
if (childPosition < 0) {
moveX = ((childPositionAbs - 1) * SIZE_SCALE_MULTIPLIER * width) + X_OFFSET;
moveX *= -1;
} else { // Moving from left to right -- sum of the previous positions' x displacements.
// X(n) = X(0) + X(1) + X(2) + ... + X(n-1)
for (int i = childPositionAbs; i > 0; i--) {
moveX += (i * SIZE_SCALE_MULTIPLIER * width);
}
moveX += X_OFFSET;
}
// Moving down y-axis is linear.
moveY = ((childPositionAbs * SIZE_SCALE_MULTIPLIER * height) / 2);
matrix.postTranslate(moveX, moveY);
// Scale alpha value.
final float alpha = (1.0f / childPositionAbs) * ALPHA_SCALE_MULTIPLIER;
t.setAlpha(alpha);
// Calculate new hit box. Since we moved the child, the hitbox is no longer lined up with the new child position.
final float newLeft = left + moveX;
final float newTop = top + moveY;
final float newRight = newLeft + (width * scaledSize);
final float newBottom = newTop + (height * scaledSize);
modifiedHitBox = new RectF(newLeft, newTop, newRight, newBottom);
} else {
modifiedHitBox = new RectF(left, top, right, bottom);
}
// update child hit box so you can tap within the child's boundary
((SetHitRectInterface) child).setHitRect(modifiedHitBox);
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Helps to smooth out jittering during scrolling.
// read more - http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
final int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if (viewsOnScreen <= 0) {
super.onLayout(changed, l, t, r, b);
}
}
private int mLastDrawnPosition;
#Override
protected int getChildDrawingOrder(int childCount, int i) {
//Reset the last position variable every time we are starting a new drawing loop
if (i == 0) {
mLastDrawnPosition = 0;
}
final int centerPosition = getSelectedItemPosition() - getFirstVisiblePosition();
if (i == childCount - 1) {
return centerPosition;
} else if (i >= centerPosition) {
mLastDrawnPosition++;
return childCount - mLastDrawnPosition;
} else {
return i;
}
}
}
File : /src/com/example/ScalingGalleryItemLayout.java
public class ScalingGalleryItemLayout extends LinearLayout implements SetHitRectInterface {
public ScalingGalleryItemLayout(Context context) {
super(context);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Rect mTransformedRect;
#Override
public void setHitRect(RectF newRect) {
if (newRect == null) {
return;
}
if (mTransformedRect == null) {
mTransformedRect = new Rect();
}
newRect.round(mTransformedRect);
}
#Override
public void getHitRect(Rect outRect) {
if (mTransformedRect == null) {
super.getHitRect(outRect);
} else {
outRect.set(mTransformedRect);
}
}
}
File : /res/layout/ScaledGalleryItemLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.ScalingGalleryItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gallery_item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/gallery_item_image"
android:layout_width="360px"
android:layout_height="210px"
android:layout_gravity="center"
android:antialias="true"
android:background="#drawable/gallery_item_button_selector"
android:cropToPadding="true"
android:padding="35dp"
android:scaleType="centerInside" />
<TextView
android:id="#+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#drawable/white"
android:textSize="30sp" />
</com.example.ScalingGalleryItemLayout>
To keep the state of the animation after it is done, just do this on your animation:
youranim.setFillAfter(true);
Edit :
In my project, I use this method and i think, it's help you :
http://developer.sonymobile.com/wp/2011/04/12/how-to-take-advantage-of-the-pinch-to-zoom-feature-in-your-xperia%E2%84%A2-10-apps-part-1/
U can do Image Zoom pinch option for gallery also.
by using below code lines:
you can download the example.
https://github.com/alvinsj/android-image-gallery/downloads
I hope this example will help to u..if u have any queries ask me.....
This is solution
integrate gallery component in android with gesture-image library
gesture-imageView
And here is full sample code
SampleCode
I Am using a marquee to show the text in one of my Activitys. My question is it possible to speed up the rate of the marquee so it scrolls along the screen faster. Below is my XML and Java.
TextView et2 = (TextView) findViewById(R.id.noneednum);
et2.setEllipsize(TruncateAt.MARQUEE);
et2.setText("");
if (num.size() > 0) {
for (String str : num) {
et2.append(str + " ");
}
}
et2.setSelected(true);
}
And XML:
<TextView
android:id="#+id/noneednum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:gravity="center_vertical|center_horizontal"
android:lines="1"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:text="Large Text"
android:textColor="#fff"
android:textSize="140dp" />
You have to create a custom class for scrolling the text:
ScrollTextView.java
public class ScrollTextView extends TextView {
// scrolling feature
private Scroller mSlr;
// milliseconds for a round of scrolling
private int mRndDuration = 10000;
// the X offset when paused
private int mXPaused = 0;
// whether it's being paused
private boolean mPaused = true;
/*
* constructor
*/
public ScrollTextView(Context context) {
this(context, null);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/**
* begin to scroll the text from the original position
*/
public void startScroll() {
// begin from the very right side
mXPaused = -1 * getWidth();
// assume it's paused
mPaused = true;
resumeScroll();
}
/**
* resume the scroll from the pausing point
*/
public void resumeScroll() {
if (!mPaused) return;
// Do not know why it would not scroll sometimes
// if setHorizontallyScrolling is called in constructor.
setHorizontallyScrolling(true);
// use LinearInterpolator for steady scrolling
mSlr = new Scroller(this.getContext(), new LinearInterpolator());
setScroller(mSlr);
int scrollingLen = calculateScrollingLen();
int distance = scrollingLen - (getWidth() + mXPaused);
int duration = (new Double(mRndDuration * distance * 1.00000
/ scrollingLen)).intValue();
setVisibility(VISIBLE);
mSlr.startScroll(mXPaused, 0, distance, 0, duration);
invalidate();
mPaused = false;
}
/**
* calculate the scrolling length of the text in pixel
*
* #return the scrolling length in pixels
*/
private int calculateScrollingLen() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
int scrollingLen = rect.width() + getWidth();
rect = null;
return scrollingLen;
}
/**
* pause scrolling the text
*/
public void pauseScroll() {
if (null == mSlr) return;
if (mPaused)
return;
mPaused = true;
// abortAnimation sets the current X to be the final X,
// and sets isFinished to be true
// so current position shall be saved
mXPaused = mSlr.getCurrX();
mSlr.abortAnimation();
}
#Override
/*
* override the computeScroll to restart scrolling when finished so as that
* the text is scrolled forever
*/
public void computeScroll() {
super.computeScroll();
if (null == mSlr) return;
if (mSlr.isFinished() && (!mPaused)) {
this.startScroll();
}
}
public int getRndDuration() {
return mRndDuration;
}
public void setRndDuration(int duration) {
this.mRndDuration = duration;
}
public boolean isPaused() {
return mPaused;
}
}
In your layout write like this:
<yourpackagename.ScrollTextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/scrolltext" />
In your activity write like this:
ScrollTextView scrolltext=(ScrollTextView) findViewById(R.id.scrolltext);
scrolltext.setText(yourscrollingtext);
scrolltext.setTextColor(Color.BLACK);
scrolltext.startScroll();
If you want to increase the scrolling speed then reduce the value of :
private int mRndDuration = 10000;//reduce the value of mRndDuration to increase scrolling speed
Above code fails if the TextView is an instance of AppCompatTextView. Below code works is it is AppCompatTextView. Tested in Marshmallow.
public static void setMarqueeSpeed(TextView tv, float speed) {
if (tv != null) {
try {
Field f = null;
if (tv instanceof AppCompatTextView) {
f = tv.getClass().getSuperclass().getDeclaredField("mMarquee");
} else {
f = tv.getClass().getDeclaredField("mMarquee");
}
if (f != null) {
f.setAccessible(true);
Object marquee = f.get(tv);
if (marquee != null) {
String scrollSpeedFieldName = "mScrollUnit";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scrollSpeedFieldName = "mPixelsPerSecond";
}
Field mf = marquee.getClass().getDeclaredField(scrollSpeedFieldName);
mf.setAccessible(true);
mf.setFloat(marquee, speed);
}
} else {
Logger.e("Marquee", "mMarquee object is null.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
This works for me. If f.get(tv) returns null, try calling mTextView.setSelected(true) before calling setMarqueeSpeed().
Original answer: Android and a TextView's horizontal marquee scroll rate
private void setMarqueeSpeed(TextView tv, float speed, boolean speedIsMultiplier) {
try {
Field f = tv.getClass().getDeclaredField("mMarquee");
f.setAccessible(true);
Object marquee = f.get(tv);
if (marquee != null) {
String scrollSpeedFieldName = "mScrollUnit";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.L)
scrollSpeedFieldName = "mPixelsPerSecond";
Field mf = marquee.getClass().getDeclaredField(scrollSpeedFieldName);
mf.setAccessible(true);
float newSpeed = speed;
if (speedIsMultiplier)
newSpeed = mf.getFloat(marquee) * speed;
mf.setFloat(marquee, newSpeed);
}
} catch (Exception e) {
e.printStackTrace();
}
}
I resolved above scrolling problems on my device running Android 7.1 taking from multiple posts here and elsewhere
Solved speed issue
Scrolling only if needed / text is longer than width of TextView
Works with extending TextView or AppCompatTextView
package com.myclass.classes;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import android.widget.TextView;
public class ScrollTextView extends TextView {
// scrolling feature
private Scroller mSlr;
// the X offset when paused
private int mXPaused = 0;
// whether it's being paused
private boolean mPaused = true;
private float mScrollSpeed = 250f; //Added speed for same scrolling speed regardless of text
/*
* constructor
*/
public ScrollTextView(Context context) {
this(context, null);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(VISIBLE);
getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(VISIBLE);
getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(VISIBLE);
getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check
}
#Override
protected void onDetachedFromWindow() {
removeGlobalListener();
super.onDetachedFromWindow();
}
/**
* begin to scroll the text from the original position
*/
private void startScroll() {
boolean needsScrolling = checkIfNeedsScrolling();
// begin from the middle
mXPaused = -1 * (getWidth() / 2);
// assume it's paused
mPaused = true;
if (needsScrolling) {
resumeScroll();
} else {
pauseScroll();
}
removeGlobalListener();
}
/**
* Removing global listener
**/
private synchronized void removeGlobalListener() {
try {
if (onGlobalLayoutListener != null)
getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
onGlobalLayoutListener = null;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Waiting for layout to initiate
*/
private OnGlobalLayoutListener onGlobalLayoutListener = () -> {
startScroll();
};
/**
* Checking if we need scrolling
*/
private boolean checkIfNeedsScrolling() {
measure(0, 0);
int textViewWidth = getWidth();
if (textViewWidth == 0)
return false;
float textWidth = getTextLength();
return textWidth > textViewWidth;
}
/**
* resume the scroll from the pausing point
*/
public void resumeScroll() {
if (!mPaused) return;
// Do not know why it would not scroll sometimes
// if setHorizontallyScrolling is called in constructor.
setHorizontallyScrolling(true);
// use LinearInterpolator for steady scrolling
mSlr = new Scroller(this.getContext(), new LinearInterpolator());
setScroller(mSlr);
int scrollingLen = calculateScrollingLen();
int distance = scrollingLen - (getWidth() + mXPaused);
int duration = (int) (1000f * distance / mScrollSpeed);
setVisibility(VISIBLE);
mSlr.startScroll(mXPaused, 0, distance, 0, duration);
invalidate();
mPaused = false;
}
/**
* calculate the scrolling length of the text in pixel
*
* #return the scrolling length in pixels
*/
private int calculateScrollingLen() {
int length = getTextLength();
return length + getWidth();
}
private int getTextLength() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
int length = rect.width();
rect = null;
return length;
}
/**
* pause scrolling the text
*/
public void pauseScroll() {
if (null == mSlr) return;
if (mPaused)
return;
mPaused = true;
// abortAnimation sets the current X to be the final X,
// and sets isFinished to be true
// so current position shall be saved
mXPaused = mSlr.getCurrX();
mSlr.abortAnimation();
}
#Override
/*
* override the computeScroll to restart scrolling when finished so as that
* the text is scrolled forever
*/
public void computeScroll() {
super.computeScroll();
if (null == mSlr) return;
if (mSlr.isFinished() && (!mPaused)) {
this.startScroll();
}
}
public boolean isPaused() {
return mPaused;
}
}
With help of the accepted answer & some changes, you can now setSpeed() even at runtime using this library.
https://github.com/RohanPatil1/SpeedMarquee
Using XML
<com.rohan.speed_marquee.SpeedMarquee
android:id="#+id/marqueeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/dummy_text"
android:maxLines="1"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marquee_speed="180.0" />
Using Methods
findViewById<Button>(R.id.speedButton).setOnClickListener {
//Increment Text3's speed by 100.0
marqueeTextView3.setSpeed(marqueeTextView3.getSpeed() + 100.0f)
}
}