Android: Up/down animated sliding menu - android

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) {
}
}
}

Related

How does this "StickyScrollView" implementation stick the view to the top of the ScrollView?

Typically how sticky headers work is that there's some sort of scrollable data that is divided into sections, each with their own header, and as you scroll down, the headers of subsequent sections replace the header at the top of the ScrollView.
What I need is to have additional sticky headers within each respective section. For example, if header1 is stuck to the top, its first section's header --header1a-- is stuck underneath it, but when we get to section 1b, 1b's header will replace 1a's, but leaving header1 stuck in the same place; and when we finally get to section 2, header2 will replace the currently stuck headers from the previous section -- header1 and header1b.
Here is a ScrollView implementation that implements sticky headers in a one-dimensional fashion:
https://github.com/emilsjolander/StickyScrollViewItems
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import java.util.ArrayList;
/**
*
* #author Emil Sj�lander - sjolander.emil#gmail.com
*
*/
public class StickyScrollView extends ScrollView {
/**
* Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc
*/
public static final String STICKY_TAG = "sticky";
/**
* Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc
*/
public static final String FLAG_NONCONSTANT = "-nonconstant";
/**
* Flag for views that have aren't fully opaque
*/
public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";
/**
* Default height of the shadow peeking out below the stuck view.
*/
private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;
private ArrayList<View> mStickyViews;
private View mCurrentlyStickingView;
private float mStickyViewTopOffset;
private int mStickyViewLeftOffset;
private boolean mRedirectTouchesToStickyView;
private boolean mClippingToPadding;
private boolean mClipToPaddingHasBeenSet;
private int mShadowHeight;
private Drawable mShadowDrawable;
private final Runnable mInvalidateRunnable = new Runnable() {
#Override
public void run() {
if(mCurrentlyStickingView !=null){
int l = getLeftForViewRelativeOnlyChild(mCurrentlyStickingView);
int t = getBottomForViewRelativeOnlyChild(mCurrentlyStickingView);
int r = getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
int b = (int) (getScrollY() + (mCurrentlyStickingView.getHeight() + mStickyViewTopOffset));
invalidate(l,t,r,b);
}
postDelayed(this, 16);
}
};
public StickyScrollView(Context context) {
this(context, null);
}
public StickyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.scrollViewStyle);
}
public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.StickyScrollView, defStyle, 0);
final float density = context.getResources().getDisplayMetrics().density;
int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
mShadowHeight = a.getDimensionPixelSize(
R.styleable.StickyScrollView_stuckShadowHeight,
defaultShadowHeightInPix);
int shadowDrawableRes = a.getResourceId(
R.styleable.StickyScrollView_stuckShadowDrawable, -1);
if (shadowDrawableRes != -1) {
mShadowDrawable = context.getResources().getDrawable(
shadowDrawableRes);
}
a.recycle();
}
/**
* Sets the height of the shadow drawable in pixels.
*
* #param height
*/
public void setShadowHeight(int height) {
mShadowHeight = height;
}
public void setup(){
mStickyViews = new ArrayList<View>();
}
private int getLeftForViewRelativeOnlyChild(View v){
int left = v.getLeft();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
left += v.getLeft();
}
return left;
}
private int getTopForViewRelativeOnlyChild(View v){
int top = v.getTop();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
top += v.getTop();
}
return top;
}
private int getRightForViewRelativeOnlyChild(View v){
int right = v.getRight();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
right += v.getRight();
}
return right;
}
private int getBottomForViewRelativeOnlyChild(View v){
int bottom = v.getBottom();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
bottom += v.getBottom();
}
return bottom;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!mClipToPaddingHasBeenSet){
mClippingToPadding = true;
}
notifyHierarchyChanged();
}
#Override
public void setClipToPadding(boolean clipToPadding) {
super.setClipToPadding(clipToPadding);
mClippingToPadding = clipToPadding;
mClipToPaddingHasBeenSet = true;
}
#Override
public void addView(View child) {
super.addView(child);
findStickyViews(child);
}
#Override
public void addView(View child, int index) {
super.addView(child, index);
findStickyViews(child);
}
#Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
super.addView(child, index, params);
findStickyViews(child);
}
#Override
public void addView(View child, int width, int height) {
super.addView(child, width, height);
findStickyViews(child);
}
#Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
super.addView(child, params);
findStickyViews(child);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(mCurrentlyStickingView != null){
canvas.save();
canvas.translate(getPaddingLeft() + mStickyViewLeftOffset, getScrollY() + mStickyViewTopOffset + (mClippingToPadding ? getPaddingTop() : 0));
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth() - mStickyViewLeftOffset,mCurrentlyStickingView.getHeight() + mShadowHeight + 1);
if (mShadowDrawable != null) {
int left = 0;
int right = mCurrentlyStickingView.getWidth();
int top = mCurrentlyStickingView.getHeight();
int bottom = mCurrentlyStickingView.getHeight() + mShadowHeight;
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(canvas);
}
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth(), mCurrentlyStickingView.getHeight());
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
mCurrentlyStickingView.draw(canvas);
hideView(mCurrentlyStickingView);
}else{
mCurrentlyStickingView.draw(canvas);
}
canvas.restore();
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction()==MotionEvent.ACTION_DOWN){
mRedirectTouchesToStickyView = true;
}
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView = mCurrentlyStickingView != null;
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView =
ev.getY()<=(mCurrentlyStickingView.getHeight()+ mStickyViewTopOffset) &&
ev.getX() >= getLeftForViewRelativeOnlyChild(mCurrentlyStickingView) &&
ev.getX() <= getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
}
}else if(mCurrentlyStickingView == null){
mRedirectTouchesToStickyView = false;
}
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, -1*((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
return super.dispatchTouchEvent(ev);
}
private boolean hasNotDoneActionDown = true;
#Override
public boolean onTouchEvent(MotionEvent ev) {
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, ((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
if(ev.getAction()==MotionEvent.ACTION_DOWN){
hasNotDoneActionDown = false;
}
if(hasNotDoneActionDown){
MotionEvent down = MotionEvent.obtain(ev);
down.setAction(MotionEvent.ACTION_DOWN);
super.onTouchEvent(down);
hasNotDoneActionDown = false;
}
if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){
hasNotDoneActionDown = true;
}
return super.onTouchEvent(ev);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
doTheStickyThing();
}
private void doTheStickyThing() {
View viewThatShouldStick = null;
View approachingStickyView = null;
for(View v : mStickyViews){
int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop());
if(viewTop<=0){
if(viewThatShouldStick==null || viewTop>(getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
viewThatShouldStick = v;
}
}else{
if(approachingStickyView == null || viewTop<(getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
approachingStickyView = v;
}
}
}
if(viewThatShouldStick!=null){
mStickyViewTopOffset = approachingStickyView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
if(viewThatShouldStick != mCurrentlyStickingView){
if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
// only compute the left offset when we start sticking.
mStickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
startStickingView(viewThatShouldStick);
}
}else if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
}
private void startStickingView(View viewThatShouldStick) {
mCurrentlyStickingView = viewThatShouldStick;
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
hideView(mCurrentlyStickingView);
}
if(((String) mCurrentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)){
post(mInvalidateRunnable);
}
}
private void stopStickingCurrentlyStickingView() {
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
}
mCurrentlyStickingView = null;
removeCallbacks(mInvalidateRunnable);
}
/**
* Notify that the sticky attribute has been added or removed from one or more views in the View hierarchy
*/
public void notifyStickyAttributeChanged(){
notifyHierarchyChanged();
}
private void notifyHierarchyChanged(){
if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
mStickyViews.clear();
findStickyViews(getChildAt(0));
doTheStickyThing();
invalidate();
}
private void findStickyViews(View v) {
if(v instanceof ViewGroup){
ViewGroup vg = (ViewGroup)v;
for(int i = 0 ; i<vg.getChildCount() ; i++){
String tag = getStringTagForView(vg.getChildAt(i));
if(tag!=null && tag.contains(STICKY_TAG)){
mStickyViews.add(vg.getChildAt(i));
}else if(vg.getChildAt(i) instanceof ViewGroup){
findStickyViews(vg.getChildAt(i));
}
}
}else{
String tag = (String) v.getTag();
if(tag!=null && tag.contains(STICKY_TAG)){
mStickyViews.add(v);
}
}
}
private String getStringTagForView(View v){
Object tagObject = v.getTag();
return String.valueOf(tagObject);
}
private void hideView(View v) {
if(Build.VERSION.SDK_INT>=11){
v.setAlpha(0);
}else{
AlphaAnimation anim = new AlphaAnimation(1, 0);
anim.setDuration(0);
anim.setFillAfter(true);
v.startAnimation(anim);
}
}
private void showView(View v) {
if(Build.VERSION.SDK_INT>=11){
v.setAlpha(1);
}else{
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(0);
anim.setFillAfter(true);
v.startAnimation(anim);
}
}
}
What I'm trying to do is to adapt it to suit my needs, but I've tried poking around in this implementation to see how it does what it does and I cannot figure out how it gets the view to get stuck to the top of the ScrollView. Does anyone have any idea how this works?
Edit:
Here is the layout that I want to apply this concept too:
*Keep in mind that the Headers (Headers 1 & 2) are custom ViewGroups that contains the Sub-Headers (Header 1a, 1b, 2a); which are also custom ViewGroups that contain custom views which are the Items.
The StickyScrollView you are using is just saving a tag to whether it should be sticky or not and if not which child of scrollview is it's header, and according to that it is maintaining it as a first child view.
If you want to use this StickyScrollView only you have to modify it and maintain one more tag as sub-header.
I will suggest rather using this ScrollView, you can use this ListView. It is very easy to implement and it works.
You can use header-decor for your requirement. Internally its using RecyclerView, so it is advisable to use it. Check Double Header section in below gif.
Hope this will help you.
This isn't rocket science. There's two key parts to understanding this.
First is in the method doTheStickyThing. This figures out what goes where.
The initial step is figuring out which header to stick. Once you scroll down, you have views both above and below the top of the scroll view. You want to stick the bottom-most header that is still above the top of the scroll view. So you see a lot of expressions like this:
getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))
That resulting value is just the offset of the top of the view from the top of the scroll view. If the header is above the top of the scroll view, the value is negative. So it turns out you want the header that has the greatest offset value that is still less than or equal to zero. The winning view gets assigned to viewThatShouldStick.
Now that you have a sticking header, you want to know which following header might start pushing that one out of the way when scrolling. That gets assigned to approachingView
If the approaching view is pushing the header out of the way, you have to offset the top of the header. That value is assigned to stickyViewTopOffset
The second key part is drawing the header. That's done in dispatchDraw.
Here's the trick to making the view look "stuck": The normal rendering logic would like to put that header at a certain place based on its current bounds. We can just move the canvas (translate) underneath that header so that it draws at the top of the scroll view instead of wherever it would normally draw. Then we tell the view to draw itself. This happens after all the list item views have been already been drawn, so the header appears to float on top of the list items.
When we move the canvas around, we also have to take into account the case where another approaching header is starting to push this one out of the way. The clipping handles some corner cases concerning how things should look when paddings are involved.
I started working on modifying the code to do what you wanted, but things got complicated fast.
Instead of tracking two headers, you need to track three headers: header, subheader, and approaching header. Now you have to handle the top offset of the subheader along with the top offset of the header. And then you have two scenarios: First is that the approaching header is a main header. This is going to modify both top offsets. But when the approaching header is a subheader, only the top offset of the pinned subheader is affected, and the main header offset stays the same.
I can get this, but I'm short on time right now. I'll finish off the code and post it if I can find the time.

Is there a way of detecting whether or not the softkeyboard is open in Android?

My app is meant to collect data. But it is only necessary for it to collect the data while the keyboard is visible.
It is not sufficient to only collect data while the user is typing, so I definitely need to know if the keyboard is visible or not.
I know, similar questions were posted before
(How to check visibility of software keyboard in Android?),
but the last answer with a serious number of upvotes is from 2012, and
I guess a lot of things happened with Android since then.
So, can I detect if the keyboard is open/visible?
Create a class
package com.dubaipolice.app.utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewTreeObserver;
import java.util.LinkedList;
import java.util.List;
/**
* Created by dev101 on 1/13/15.
*/
public class SoftKeyboardStateHelper implements ViewTreeObserver.OnGlobalLayoutListener {
float LIMIT = 100;
public interface SoftKeyboardStateListener {
void onSoftKeyboardOpened(int keyboardHeightInPx);
void onSoftKeyboardClosed();
}
private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
private final View activityRootView;
private int lastSoftKeyboardHeightInPx;
private boolean isSoftKeyboardOpened;
public SoftKeyboardStateHelper(Context context, View activityRootView) {
this(activityRootView, false);
Resources r = context.getResources();
LIMIT = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, r.getDisplayMetrics());
}
public SoftKeyboardStateHelper(View activityRootView, boolean isSoftKeyboardOpened) {
this.activityRootView = activityRootView;
this.isSoftKeyboardOpened = isSoftKeyboardOpened;
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
final Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (!isSoftKeyboardOpened && heightDiff > LIMIT) { // if more than 100 pixels, its probably a keyboard...
isSoftKeyboardOpened = true;
notifyOnSoftKeyboardOpened(heightDiff);
} else if (isSoftKeyboardOpened && heightDiff < LIMIT) {
isSoftKeyboardOpened = false;
notifyOnSoftKeyboardClosed();
}
}
public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
this.isSoftKeyboardOpened = isSoftKeyboardOpened;
}
public boolean isSoftKeyboardOpened() {
return isSoftKeyboardOpened;
}
/**
* Default value is zero (0)
*
* #return last saved keyboard height in px
*/
public int getLastSoftKeyboardHeightInPx() {
return lastSoftKeyboardHeightInPx;
}
public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
listeners.add(listener);
}
public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
listeners.remove(listener);
}
private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;
for (SoftKeyboardStateListener listener : listeners) {
if (listener != null) {
listener.onSoftKeyboardOpened(keyboardHeightInPx);
}
}
}
private void notifyOnSoftKeyboardClosed() {
for (SoftKeyboardStateListener listener : listeners) {
if (listener != null) {
listener.onSoftKeyboardClosed();
}
}
}
}
Then in your activity's onCreate, add following lines
final SoftKeyboardStateHelper softKeyboardStateHelper = new SoftKeyboardStateHelper(context, findViewById(R.id.parent));
softKeyboardStateHelper.addSoftKeyboardStateListener(softKeyboardStateListener);
where R.id.parent is the id of your activity's parent layout and softKeyboardStateListener is defined as follows
SoftKeyboardStateHelper.SoftKeyboardStateListener softKeyboardStateListener = new SoftKeyboardStateHelper.SoftKeyboardStateListener() {
#Override
public void onSoftKeyboardOpened(int keyboardHeightInPx) {
}
#Override
public void onSoftKeyboardClosed() {
}
};
We have currently Android N now and still no direct ways to detect if keyboard is opened or not. There're only work around solutions available like the ones checking the screen size. However there're not fullproof and some times give false signals for example on screen rotation or going into multi-window mode on Android N.
As you may have heard, there is no direct way. However, by checking if the screen size has been changed, you can generally find this out by using:
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
if (yOld > yNew) {
//Do Stuff Here
}
}
Hope I Helped :D
I faced the same problem earlier. After a lot of trial and error and following other people suggestion, I came up with my implementation that works well for me. Here is the link.

the Navigation that facebook app is using

You must have used the facebook android app. I want to implement the same navigation that the facebook is using like:
on swipping left the menu is opened and swipping right the chat list is shown. And in the middle the activities and layouts keep on changing. But I am confused how to make such a navigation. (Most noticable thing is that the middle page is half shown when the swipping is done on left or right.) help?
You have two good options.
Navigation Drawer: http://developer.android.com/design/patterns/navigation-drawer.html
Sliding Drawer: https://github.com/jfeinstein10/SlidingMenu
You can make multiple navigation drawers for your use case.
I installed the FB app an hour ago. When you tap the icon in the top-right corner of the main content, the main content moves left to reveal content on the right. The revealed content remains fixed and behind the main content during the move.
I wrote this lightweight abstraction based on the above analysis:
package org.yourdomain.app;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
abstract public class NavigatorActivity extends Activity {
static public final String TAG = "NavigatorActivity";
static public final Boolean DEBUG_LIFECYCLE = false;
/**
* True when menu is on and vice versa
*/
private boolean mToggled;
/**
* Percentage of the main content to keep shown
*/
private float mDistance;
/**
* Navigator listener
*/
private NavigatorListener mListener;
/**
* Content view
*/
private ViewGroup mContentLayout;
/**
* Menu frame
*/
private FrameLayout mMenuLayout;
/**
* Main frame
*/
private FrameLayout mMainLayout;
/**
* Speed of toggle animation
*/
private long mSpeed;
/**
* Width of the content view
*/
private int mContentWidth;
/**
* Height of the content view
*/
private int mContentHeight;
/**
* The current distance to slide the main frame
*/
private int mToggleDistance;
/**
*
*/
public NavigatorActivity() {
mToggled = false;
mDistance = 80;
mSpeed = 300l;
}
/**
* Speed setter.
*
* Controls how fast the menu frame is revealed.
*
* #param speed
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
/**
* Distance setter.
*
* The distance is the % of the oriented screen to pull the main frame in order to reveal the
* menu frame.
*
* #param distance
*/
public void setDistance(float distance) {
mDistance = distance;
}
/**
* Navigator listener setter.
*
* #param listener
*/
public void setNavigatorListener(NavigatorListener listener) {
mListener = listener;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG_LIFECYCLE) Log.v(TAG, "onCreate " + this + ": " + savedInstanceState);
initContentLayout();
setContentView(mContentLayout);
initMenuLayout();
mContentLayout.addView(mMenuLayout);
initMainLayout();
mContentLayout.addView(mMainLayout);
}
/**
* Initializes the main frame.
*/
private void initMainLayout() {
int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mMainLayout = new FrameLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mContentWidth, mContentHeight);
}
};
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(hw, hw);
p.addRule(RelativeLayout.ALIGN_PARENT_TOP);
p.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
mMainLayout.setLayoutParams(p);
}
/**
* Initializes the menu frame.
*/
private void initMenuLayout() {
int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mMenuLayout = new FrameLayout(this);
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(hw, hw);
p.addRule(RelativeLayout.ALIGN_PARENT_TOP);
p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mMenuLayout.setLayoutParams(p);
}
/**
* Initialize the activity's content layout.
*/
private void initContentLayout() {
final int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mContentLayout = new RelativeLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mContentWidth = MeasureSpec.getSize(widthMeasureSpec);
mContentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mContentWidth, mContentHeight);
mToggleDistance = Math.round((mDistance / 100f) * (float) mContentWidth);
}
};
mContentLayout.setLayoutParams(new RelativeLayout.LayoutParams(hw, hw));
}
/**
* Inflates two project XML layout and adds them to the menu and main layout frames.
*
* #param menuLayoutResId
* #param mainLayoutResId
*/
protected void setContentViews(int menuLayoutResId, int mainLayoutResId) {
final LayoutInflater inflater = getLayoutInflater();
inflater.inflate(menuLayoutResId, mMenuLayout);
inflater.inflate(mainLayoutResId, mMainLayout);
}
/**
* Toggle the menu frame.
*/
final public void toggleNavigator() {
final boolean isToggled = mToggled = !mToggled;
if (DEBUG_LIFECYCLE) Log.v(TAG, "toggleNavigatorMenu " + this);
RelativeLayout.LayoutParams menuParams = (RelativeLayout.LayoutParams) mMenuLayout.getLayoutParams();
menuParams.setMargins(mContentWidth - mToggleDistance, 0, 0, 0);
final RelativeLayout.LayoutParams mainParams = (RelativeLayout.LayoutParams) mMainLayout.getLayoutParams();
ValueAnimator animator = ValueAnimator.ofInt(mainParams.leftMargin, mToggled ? -mToggleDistance : 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mainParams.leftMargin = (Integer) valueAnimator.getAnimatedValue();
mMainLayout.requestLayout();
}
});
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
if (mListener != null) {
mListener.onNavigatorToggled(isToggled);
}
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
});
animator.setDuration(mSpeed);
animator.start();
}
public static interface NavigatorListener {
public void onNavigatorToggled(boolean isToggled);
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
toggleNavigator();
}
return super.onKeyUp(keyCode, event);
}
}
Example usage:
package org.yourdomain.project;
import android.os.Bundle;
import org.yourdomain.app.NavigatorActivity;
public class YourActivity extends NavigatorActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDistance(80); // will leave 20% of the main content in view
setNavigatorListener(new NavigatorListener() {
#Override
public void onNavigatorToggled(boolean isToggled) {
// Load content dynamically, like FB does?
}
});
setContentViews(R.layout.layout_menu, R.layout.activity_your);
}
}
compileSdkVersion 20
minSdkVersion 16
targetSdkVersion 20
Clicking on the physical menu button on your Android device will toggle the navigator.
I plan on using this in a couple of startup projects of my own.
Let me know if you have any questions. I hope this helps you.

Pull to refresh in Android without popular library

I want to implement pull-to-refresh in Android app, but I don't want to use pull-to-refresh library which is available on the internet because it is too slow for the gridView I am using. So I want to implement it by hand, do you know how to do this? Or which methods should I use from GridView?
PullRefreshContainerView.java
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
/**
* A container for a ListView that can be pulled to refresh.
* This will create a ListView and refresh header automatically, but you can
* customize them by using {#link #setList(ListView)} and {#link #setRefreshHeader(View, int)}
* <p>
* To use, put this where you would normally put your ListView. Since this does not extend
* ListView, you must use {#link #getList()} to modify the list or provide your own.
* <p>
* To get the actions of the list, use a {#link OnChangeStateListener} with {#link #setOnChangeStateListener(OnChangeStateListener)}.
* If you want to change how the refresh header looks, you should do it during these state changes.
*/
public class PullRefreshContainerView extends LinearLayout {
/**
* Interface for listening to when the refresh container changes state.
*/
public interface OnChangeStateListener {
/**
* Notifies a listener when the refresh view's state changes.
* #param container The container that contains the header
* #param state The state of the header. May be STATE_IDLE, STATE_READY,
* or STATE_REFRESHING.
*/
public void onChangeState(PullRefreshContainerView container, int state);
}
/**
* State of the refresh header when it is doing nothing or being pulled down slightly.
*/
public static final int STATE_IDLE = 0;
/**
* State of the refresh header when it has been pulled down but not enough to start refreshing, and
* has not yet been released.
*/
public static final int STATE_PULL = 1;
/**
* State of the refresh header when it has been pulled down enough to start refreshing, but
* has not yet been released.
*/
public static final int STATE_RELEASE = 2;
/**
* State of the refresh header when the list should be refreshing.
*/
public static final int STATE_LOADING = 3;
private LinearLayout mHeaderContainer;
private View mHeaderView;
private ListView mList;
private int mState;
private OnChangeStateListener mOnChangeStateListener;
private int REFRESH_VIEW_HEIGHT = 60;
/**
* Creates a new pull to refresh container.
*
* #param context the application context
*/
public PullRefreshContainerView(Context context) {
super(context);
init(context);
}
/**
* Creates a new pull to refresh container.
*
* #param context the application context
* #param attrs the XML attribute set
*/
public PullRefreshContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Creates a new pull to refresh container.
*
* #param context the application context
* #param attrs the XML attribute set
* #param defStyle the style for this view
*/
public PullRefreshContainerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mState = STATE_IDLE; // Start out as idle.
float densityFactor = context.getResources().getDisplayMetrics().density;
REFRESH_VIEW_HEIGHT *= densityFactor;
// We don't want to see the fading edge on the container.
setVerticalFadingEdgeEnabled(false);
setVerticalScrollBarEnabled(false);
setOrientation(LinearLayout.VERTICAL);
// Set the default list and header.
mHeaderContainer = new LinearLayout(context);
addView(mHeaderContainer);
setRefreshViewHeight(1);
TextView headerView = new TextView(context);
headerView.setText("Default refresh header.");
setRefreshHeader(headerView);
ListView list = new ListView(context);
setList(list);
}
private boolean mScrollingList = true;
private float mInterceptY;
private int mLastMotionY;
#Override
public boolean dispatchTouchEvent (MotionEvent ev) {
float oldLastY = mInterceptY;
mInterceptY = ev.getY();
if (mState == STATE_LOADING) {
return super.dispatchTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionY = (int) ev.getY();
mScrollingList = true;
return super.dispatchTouchEvent(ev);
case MotionEvent.ACTION_MOVE:
if (mList.getFirstVisiblePosition() == 0
&& (mList.getChildCount() == 0 || mList.getChildAt(0).getTop() == 0)) {
if ((mInterceptY - oldLastY > 5) || (mState == STATE_PULL) || (mState == STATE_RELEASE)) {
mScrollingList = false;
applyHeaderHeight(ev);
return true;
} else {
mScrollingList = true;
return super.dispatchTouchEvent(ev);
}
} else if (mScrollingList) {
return super.dispatchTouchEvent(ev);
} else {
return super.dispatchTouchEvent(ev);
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mState == STATE_RELEASE) {
refresh();
} else {
changeState(STATE_IDLE);
}
if (mScrollingList) {
return super.dispatchTouchEvent(ev);
} else {
return true;
}
default:
return super.dispatchTouchEvent(ev);
}
}
private void applyHeaderHeight(MotionEvent ev) {
final int historySize = ev.getHistorySize();
if (historySize > 0) {
for (int h = 0; h < historySize; h++) {
int historicalY = (int) (ev.getHistoricalY(h));
updateRefreshView(historicalY - mLastMotionY);
}
} else {
int historicalY = (int) ev.getY();
updateRefreshView(historicalY - mLastMotionY);
}
}
private void updateRefreshView(int height) {
if (height <= 0) {
return;
}
if ((REFRESH_VIEW_HEIGHT/4 <= mCurRefreshViewHeight) && (mCurRefreshViewHeight < REFRESH_VIEW_HEIGHT)) {
setRefreshViewHeight(height);
changeState(STATE_PULL);
} else if (mCurRefreshViewHeight >= REFRESH_VIEW_HEIGHT) {
if (height > REFRESH_VIEW_HEIGHT) {
height = (int) (REFRESH_VIEW_HEIGHT + (height - REFRESH_VIEW_HEIGHT) * REFRESH_VIEW_HEIGHT * 1.0f/height);
}
setRefreshViewHeight(height);
changeState(STATE_RELEASE);
} else {
setRefreshViewHeight(height);
}
}
private int mCurRefreshViewHeight = 60;
private void setRefreshViewHeight(int height) {
if (mCurRefreshViewHeight == height) {
return;
}
if (height == 1) {
mHeaderContainer.setLayoutParams(new LayoutParams(1, 1));
} else {
mHeaderContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, height));
}
mCurRefreshViewHeight = height;
}
private void changeState(int state) {
switch (state) {
case STATE_IDLE:
setRefreshViewHeight(1);
break;
case STATE_PULL:
break;
case STATE_RELEASE:
break;
case STATE_LOADING:
setRefreshViewHeight(REFRESH_VIEW_HEIGHT);
break;
}
mState = state;
notifyStateChanged();
}
/**
* Sets the list to be used in this pull to refresh container.
* #param list the list to use
*/
public void setList(ListView list) {
if (mList != null) {
removeView(mList);
}
mList = list;
if (mList.getParent() != null) {
ViewGroup parent = (ViewGroup) mList.getParent();
parent.removeView(mList);
}
mList.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(mList);
}
/**
* #return the list inside this pull to refresh container
*/
public ListView getList() {
return mList;
}
/**
* Sets the view to use as the refresh header.
* <p />
* The header view is the view at the top that will show while the list
* is refreshing. Usually, this will be a simple rectangle that says "refreshing" and the like.
* <p />
*
* #param headerView the view to use as the whole header.
*/
public void setRefreshHeader(View header) {
if (mHeaderView != null) {
mHeaderContainer.removeView(mHeaderView);
}
if (header == null) {
throw new RuntimeException("Please supply a non-null header container.");
}
mHeaderContainer.addView(header, 0);
mHeaderView = header;
}
public void refresh() {
changeState(STATE_LOADING);
}
/**
* Notifies the pull-to-refresh view that the refreshing is complete.
* This will hide the refreshing header.
*/
public void completeRefresh() {
changeState(STATE_IDLE);
}
/**
* Notifies the listener that the state has changed.
*/
private void notifyStateChanged() {
if (mOnChangeStateListener != null) {
mOnChangeStateListener.onChangeState(this, mState);
}
}
/**
* #param listener the listener to be notified when the header state should change
*/
public void setOnChangeStateListener(OnChangeStateListener listener) {
mOnChangeStateListener = listener;
}
}
How To use
UsageDemoActivity.java
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import com.dmobile.pulltorefresh.PullRefreshContainerView.OnChangeStateListener;
import com.dmobile.pulltorefresh.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class UsageDemoActivity extends Activity {
private PullRefreshContainerView mContainerView;
private TextView mRefreshHeader;
private ListView mList;
private ArrayList<String> mStrings = new ArrayList<String>();
private ArrayAdapter<String> mAdapter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mRefreshHeader = new TextView(this);
mRefreshHeader.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mRefreshHeader.setGravity(Gravity.CENTER);
mRefreshHeader.setText("Pull to refresh...");
mContainerView = (PullRefreshContainerView) findViewById(R.id.container);
mContainerView.setRefreshHeader(mRefreshHeader);
mContainerView.setOnChangeStateListener(new OnChangeStateListener() {
#Override
public void onChangeState(PullRefreshContainerView container, int state) {
switch (state) {
case PullRefreshContainerView.STATE_IDLE:
case PullRefreshContainerView.STATE_PULL:
mRefreshHeader.setText("Pull to refresh...");
break;
case PullRefreshContainerView.STATE_RELEASE:
mRefreshHeader.setText("Release to refresh...");
break;
case PullRefreshContainerView.STATE_LOADING:
mRefreshHeader.setText("Loading...");
final Timer t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
UsageDemoActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
addStrings(1);
mContainerView.completeRefresh();
t.cancel();
}
});
}
}, 5000, 5000);
break;
}
}
});
mList = mContainerView.getList();
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mStrings);
mList.setAdapter(mAdapter);
addStrings(3);
}
private void addStrings(int count) {
int curSize = mStrings.size();
for (int i = 0; i < count; ++i) {
mStrings.add("String " + (curSize + i));
}
mAdapter.notifyDataSetChanged();
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.dmobile.pulltorefresh.PullRefreshContainerView
android:id="#+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
UPDATE TO WORK WITH GRIDVIEW
PullRefreshContainerView.java
private void init(Context context) {
mState = STATE_IDLE; // Start out as idle.
float densityFactor = context.getResources().getDisplayMetrics().density;
REFRESH_VIEW_HEIGHT *= densityFactor;
// We don't want to see the fading edge on the container.
setVerticalFadingEdgeEnabled(false);
setVerticalScrollBarEnabled(false);
setOrientation(LinearLayout.VERTICAL);
// Set the default list and header.
mHeaderContainer = new LinearLayout(context);
addView(mHeaderContainer);
setRefreshViewHeight(1);
TextView headerView = new TextView(context);
headerView.setText("Default refresh header.");
setRefreshHeader(headerView);
GridView grid = new GridView(context);
setList(grid);
}
And also make related change in setListMethod();

Handling click events on a drawable within an EditText

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;
}
}
};

Categories

Resources