I've been working on this for a while. The Idea started simple, I wanted a button on a SlidingDrawer handle to allow the user to view settings specific to the content of the drawer. So I made a layout with a button off to the side and set it as the handle. The drawer drew fine, but would not allow the button (on the handle) to be pressed. When ever I try to click the thing, the click is interpreted as a handle click, and toggle the state of the drawer.
Does anyone know whats going on?
Thanks ~Aedon
I'll post my implementation, to save others the trouble.
You basically have to extend the SlidingDrawer class and handle the onInterceptTouch events to pass through when they're on top of items inside the handle layout.
This assumes you are using a ViewGroup (e.g. any layout) for the handle and all the views inside it are clickable.
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SlidingDrawer;
public class ClickableSlidingDrawer extends SlidingDrawer {
private ViewGroup mHandleLayout;
private final Rect mHitRect = new Rect();
public ClickableSlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ClickableSlidingDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
View handle = getHandle();
if (handle instanceof ViewGroup) {
mHandleLayout = (ViewGroup) handle;
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mHandleLayout != null) {
int childCount = mHandleLayout.getChildCount();
int handleClickX = (int)(event.getX() - mHandleLayout.getX());
int handleClickY = (int)(event.getY() - mHandleLayout.getY());
Rect hitRect = mHitRect;
for (int i=0;i<childCount;i++) {
View childView = mHandleLayout.getChildAt(i);
childView.getHitRect(hitRect);
if (hitRect.contains(handleClickX, handleClickY)) {
return false;
}
}
}
return super.onInterceptTouchEvent(event);
}
}
Then, in your layout .xml just use <my.package.name.ClickableSlidingDrawer> instead of <SlidingDrawer>
I tried out d4n3's implementation, but since my handle contains a button that is nested within multiple ViewGroups, I had to modify it to make it work.
My implementations also assumes that you are using a ViewGroup for the handle, but the child views don't have to be clickable. Also, you have to set the tag to "click_intercepted" of the View(s) that you want to be clickable in the handle. Only child views with this specific tag set will be considered for clicks within the handle. This way, you can layout your handle anyway you want, and still act appropriately on clicks on specific Views (e.g. a Button) in the handle. Also, with this implementation, you can still both drag and click the handle to toggle its state.
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SlidingDrawer;
public class ClickableSlidingDrawer extends SlidingDrawer
{
private static final String TAG_CLICK_INTERCEPTED = "click_intercepted";
private ViewGroup mHandleLayout;
private final Rect mHitRect = new Rect();
public ClickableSlidingDrawer(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ClickableSlidingDrawer(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected void onFinishInflate()
{
super.onFinishInflate();
View handle = getHandle();
if (handle instanceof ViewGroup)
{
mHandleLayout = (ViewGroup) handle;
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
if (mHandleLayout != null)
{
int clickX = (int) (event.getX() - mHandleLayout.getLeft());
int clickY = (int) (event.getY() - mHandleLayout.getTop());
if (isAnyClickableChildHit(mHandleLayout, clickX, clickY))
{
return false;
}
}
return super.onInterceptTouchEvent(event);
}
private boolean isAnyClickableChildHit(ViewGroup viewGroup, int clickX, int clickY)
{
for (int i = 0; i < viewGroup.getChildCount(); i++)
{
View childView = viewGroup.getChildAt(i);
if (TAG_CLICK_INTERCEPTED.equals(childView.getTag()))
{
childView.getHitRect(mHitRect);
if (mHitRect.contains(clickX, clickY))
{
return true;
}
}
if (childView instanceof ViewGroup && isAnyClickableChildHit((ViewGroup) childView, clickX, clickY))
{
return true;
}
}
return false;
}
}
You can suppress the action that interprets a click on the handle button as an "open" with an attribute in the SlidingDrawer element in the layout XML. Like this:
<SlidingDrawer android:layout_width="fill_parent"android:id="#+id/SlidingDrawer" android:handle="#+id/slideHandleButton"
android:content="#+id/txtHolder" android:layout_height="fill_parent"
android:orientation="horizontal" android:allowSingleTap="false">
Just make the android:allowSingleTap="false" Then just implement a click handler for the button like you normally would. This will stop it from opening/closing the drawer, but you might need to intercept the events for the button to get it to do what YOU want it to do.
First make a layout and put your Handle content in it (say you put in handle_content.xml).
Second replace your current handle handle with this:
<include android:id="#id/handle"
android:layout="#layout/handle_content.xml"/>
Now do as below (I say this because below work correctly if u do as above)
This is my implementation:
package com.examples.my.views;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.SlidingDrawer;
import com.examples.my.MainFragmentActivity;
public class MYSlidingDrawer extends SlidingDrawer {
private View button;
private int height;
private MainFragmentActivity activity;
public MYSlidingDrawer (Context context, AttributeSet attrs) {
super(context, attrs);
DisplayMetrics metrics = this.getResources().getDisplayMetrics();
height = metrics.heightPixels;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
int left = button.getLeft();
int top = button.getTop();
int right = button.getRight();
int bottom = button.getBottom();
Rect rect = new Rect(left, top, right, bottom);
int x = (int) event.getX();
int y = (int) event.getY();
if (isOpened()) {
if (rect.contains(x, y)) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (activity != null)
{
//HERE DO YOUR WORK
// Like activity.tooglePlay();
}
}
return true;
}
} else {
y -= height;
if (rect.contains(x, Math.abs(y))) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (activity != null)
{
//HERE DO YOUR WORK
// Like activity.tooglePlay();
}
}
return true;
}
}
return super.onTouchEvent(event);
}
public void setButton(View button) {
this.button = button;
}
public void setActivity(MainFragmentActivity activity) {
this.activity = activity;
}
}
And now define this in which you include MYSlidingDrawer:
MYSlidingDrawer drawer = (MYSlidingDrawer) findViewById(R.id.drawer);
drawer.setActivity(this);
Button btn = (Button) findViewById(R.id.play_btn);//button inside your handle
drawer.setButton(btn);
Hope this help you.
Related
How can one detect click events on compound drawables of a TextInputEditText?
Use the following overriden version of TextInputEditText, and call setOnDrawableClickedListener.
You may fare better if you set your drawable at the end of the edit text than at the start, because the current version of TextInputLayout produces fairly ugly results when the drawable is at the start.
Sample layout is given further down. (Note the use of android:drawablePadding="10dp" particularly).
Code is for androidx, but you can backport to AppCompat trivially.
package com.twoplay.netplayer.controls;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.google.android.material.textfield.TextInputEditText;
public class TextInputEditTextEx extends TextInputEditText {
private OnDrawableClickedListener onDrawableClickedListener;
public TextInputEditTextEx(Context context) {
super(context);
init();
}
public TextInputEditTextEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TextInputEditTextEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOnTouchListener(new OnTouchListener() {
private Rect hitBounds = new Rect();
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
int hitDrawable = -1;
if (x < getCompoundPaddingLeft())
{
hitDrawable = 0;
hitBounds.set(0,0,getCompoundPaddingLeft(),getHeight());
}
if (x > getWidth()-getCompoundPaddingRight())
{
hitDrawable = 2;
hitBounds.set(getCompoundPaddingRight(),0,getWidth(),getHeight());
}
if (hitDrawable != -1)
{
int action = event.getAction();
if (action == MotionEvent.ACTION_UP)
{
onDrawableClicked(hitDrawable,hitBounds);
}
return true;
}
return false;
}
});
}
private void onDrawableClicked(int i, Rect bounds) {
if (onDrawableClickedListener != null)
{
onDrawableClickedListener.onDrawableClicked(this,i,bounds);
}
}
public interface OnDrawableClickedListener {
void onDrawableClicked(View v, int drawable, Rect bounds);
}
public void setOnDrawableClickedListener(OnDrawableClickedListener listener)
{
this.onDrawableClickedListener = listener;
}
}
Sample layout:
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/playlist_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/playlist_name" >
<com.twoplay.netplayer.controls.TextInputEditTextEx
android:id="#+id/playlist_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:drawableEnd="#drawable/ic_more_horiz_black_24dp"
android:drawablePadding="10dp" />
</com.google.android.material.textfield.TextInputLayout>
I am trying to implement Locker App and swipe unlock functionality using android seekbar.
What I want is, swipe to unlock from right to left and if user release touch from drawable then it should come back to right side of the layout.
Using following code, I am able to achieve the same but for Left to Right direction, I want to do the same in opposite direction i.e Right to Left.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;
public class SlideButton extends SeekBar {
private Drawable thumb;
private SlideButtonListener listener;
public SlideButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
this.thumb = thumb;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (thumb.getBounds().contains((int) event.getX(), (int) event.getY())) {
super.onTouchEvent(event);
} else
return false;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (getProgress() > 70)
handleSlide();
setProgress(0);
} else
super.onTouchEvent(event);
return true;
}
private void handleSlide() {
listener.handleSlide();
}
public void setSlideButtonListener(SlideButtonListener listener) {
this.listener = listener;
}
}
interface SlideButtonListener {
public void handleSlide();
}
NOTE: In above code when user click on drawable to swipe from left to right, and if he releases his finger from drawable, with seekbar Progress < 70, then the icon/drawable comes back to original position i.e to left corner.
So, my question is simple how could I achieve same functionality using above code to swipe unlock from right to left.
I have tried to achieve this using following code, it do shows me drawable icon to right side but when i try to swipe it from right to left it doesn't functions as expected.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;
public class ReversedSeekBar extends SeekBar {
private Drawable thumb;
private SlideButtonListener listener;
public ReversedSeekBar(Context context) {
super(context);
}
public ReversedSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReversedSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
this.thumb = thumb;
}
#Override
protected void onDraw(Canvas canvas) {
float px = this.getWidth() / 2.0f;
float py = this.getHeight() / 2.0f;
canvas.scale(-1, 1, px, py);
//canvas.rotate(180, px, py);
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (thumb.getBounds().contains((int) event.getX(), (int) event.getY())) {
super.onTouchEvent(event);
} else
return false;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (getProgress() > 70)
handleSlide();
setProgress(0);
} else
super.onTouchEvent(event);
return true;
}
private void handleSlide() {
listener.handleSlide();
}
public void setSlideButtonListener(SlideButtonListener listener) {
this.listener = listener;
}
}
Thanks...
Try adding these two lines to xml. (It did the trick for me)
android:layoutDirection="rtl"
android:mirrorForRtl="true"
In Kotlin:
Check the default language code containing "ar"
when {
Locale.getDefault().language == "ar" -> {
clSeekbar.scaleX = -1
}
I have this activity that detects Multi-Touch and counts each instance that the device is tapped over 3 times. What I want is to do a check in Main to see if it reaches the limit that have been set.
Main Activity
package com.test.multitouch;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// DETECT more than 20 counts here and display a toast
}
Custom View which extends view
package com.test.multitouch;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
#SuppressLint("ClickableViewAccessibility")
public class custom_view extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
final int MAX_NUMBER_OF_POINT = 4;
float[] x = new float[MAX_NUMBER_OF_POINT];
float[] y = new float[MAX_NUMBER_OF_POINT];
boolean[] touching = new boolean[MAX_NUMBER_OF_POINT];
int count = 0;
long cur = System.currentTimeMillis();
long dur = 30000;
long fut = cur + dur;
public custom_view(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public custom_view(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public custom_view(Context context) {
super(context);
init();
}
void init() {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(40);
}
#Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < MAX_NUMBER_OF_POINT; i++) {
if (touching[i]) {
switch (i) {
case 1:
paint.setColor(Color.BLUE);
break;
case 2:
paint.setColor(Color.RED);
break;
case 3:
paint.setColor(Color.YELLOW);
break;
case 4:
paint.setColor(Color.GREEN);
break;
}
canvas.drawCircle(x[i], y[i], 70f, paint);
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = (event.getAction() & MotionEvent.ACTION_MASK);
int pointCount = event.getPointerCount();
if (pointCount > 3) {
Log.i("LOG", "register multi touch");
count++;
if (count > 20) {
//SEND BACK TO MAIN TO SAY IT HAS BEEN ACHIEVED
}
}
for (int i = 0; i < pointCount; i++) {
int id = event.getPointerId(i);
if (id < MAX_NUMBER_OF_POINT) {
x[id] = (int) event.getX(i);
y[id] = (int) event.getY(i);
if ((action == MotionEvent.ACTION_DOWN)
|| (action == MotionEvent.ACTION_POINTER_DOWN)
|| (action == MotionEvent.ACTION_MOVE)) {
touching[id] = true;
} else {
touching[id] = false;
}
}
}
invalidate();
return true;
}
}
Your custom view has a reference to the context it's attached to. I assume this view is running in the same activity you are trying to reference. You can get the reference to the activity as follows:
MainActivity mainActivity = (MainActivity) custom_view.this.getContext();
I also assumed you are calling this from inside your onTouchListener. You need to reference the "this" to get to the outterclass instance, otherwise this will refer to the instance of your onTouchListener class instead. Also, you should capitalize class names, so you can recognize from the capitalization whether we are referring to an instance or class definition.
Once you have the reference, you can call some function on main Activity to update it.
mainActivity.callSomeFunction();
I'm trying to achieve this effect that can be seen above on StickyListHeaders's sample app:
Basically I need to show a single, static, fixed header view on top of a ListView but bellow its scrollbar. I don't need anything related to sections or alphabetical indexing or anything like that.
I'm unable to figure out how to do this based on the source code of StickyListHeaders. I tried subclassing ListView and overriding dispatchDraw() like this:
protected void dispatchDraw(Canvas canvas)
{
View view = LayoutInflater.from(getContext()).inflate(R.layout.header, this, false);
drawChild(canvas, view, getDrawingTime());
super.dispatchDraw(canvas);
}
But it doesn't work, no header is drawn.
Answering my own question. This ListView subclass is able to do what I wanted. The first element of the list can become fixed calling showFixedHeader():
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
public class FixedHeaderListView extends ListView
{
private View fixedHeader = null;
private boolean fixedHeaderLayoutDone = false;
private boolean showFixedHeader = true;
#SuppressWarnings("unused")
public FixedHeaderListView(Context context)
{
super(context);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void showFixedHeader(boolean show)
{
this.showFixedHeader = show;
requestLayout(); // Will cause layoutChildren() and dispatchDraw() to be called
}
#Override
protected void layoutChildren()
{
super.layoutChildren();
if (!fixedHeaderLayoutDone)
{
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0)
{
// Layout the first item in the adapter's data set as the fixed header
fixedHeader = adapter.getView(0, null, this);
if (fixedHeader != null)
{
// Measure and layout
LayoutParams layoutParams = (LayoutParams)fixedHeader.getLayoutParams();
if (layoutParams == null)
{
layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
int heightMode = MeasureSpec.getMode(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED)
{
heightMode = MeasureSpec.EXACTLY;
}
int heightSize = MeasureSpec.getSize(layoutParams.height);
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize > maxHeight)
{
heightSize = maxHeight;
}
int widthSpec = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
fixedHeader.measure(widthSpec, heightSpec);
fixedHeader.layout(0, 0, fixedHeader.getMeasuredWidth(), fixedHeader.getMeasuredHeight());
// Flag as layout done
fixedHeaderLayoutDone = true;
}
}
}
}
#Override #SuppressWarnings("NullableProblems")
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
if (fixedHeader != null && showFixedHeader)
{
drawChild(canvas, fixedHeader, getDrawingTime());
}
}
}
It's not heavily tested, but it's a good starting point.
This is my updated code. It doesn't detect movement at all now. Maybe I shouldn't be making each Image an instance? Basically I want to user to be able to swipe through all the images to make them dissapear.
Thanks for all the help.
package com.picomputing.mythirdapplication;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
/**
* Created by Paul on 8/13/13.
*/
public class Pin extends ImageView implements View.OnTouchListener {
boolean isPinDown;
public Pin(Context context) {
super(context);
this.isPinDown = false;
}
public Pin(Context context, AttributeSet attrs) {
super(context, attrs);
this.isPinDown = false;
}
public Pin(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.isPinDown = false;
}
public boolean pinDown() {
return this.isPinDown;
}
public void setPinDown() {
this.isPinDown = true;
}
public void setPinUp() {
this.isPinDown = false;
}
public void togglePin() {
if (isPinDown == false)
{
isPinDown = true;
this.setImageResource(Color.TRANSPARENT);
}
else
{
isPinDown = false;
this.setImageResource(R.drawable.pin);
}
}
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX(); //--relative to mLayout--
int y = (int) event.getY(); //--relative to mLayout--
Rect r = new Rect();
view.getHitRect(r);
if(r.contains(x,y) && view instanceof ImageView){
togglePin();
}
}
return true;
}
}
You need to listen and consume ACTION_MOVE events, for the parent view of whatever you are trying to change.
Here's an example with a couple of ImageViews in a LinerLayout as a parent:
public class test extends Activity {
LinearLayout mLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLayout = new LinearLayout(this);
mLayout.setOrientation(LinearLayout.VERTICAL);
for(int i = 0 ; i < 5; i++){
ImageView iv = new ImageView(this);
iv.setImageResource(android.R.drawable.ic_dialog_info);
mLayout.addView(iv);
}
setContentView(mLayout);
mLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX(); //--relative to mLayout--
int y = (int) event.getY(); //--relative to mLayout--
Rect r = new Rect();
for(int i = 0 ; i < mLayout.getChildCount(); i++){
View v = mLayout.getChildAt(i);
v.getHitRect(r);
if(r.contains(x,y) && v instanceof ImageView){
((ImageView) v).setImageResource(android.R.drawable.ic_dialog_alert);
}
}
}
return true; //-- this means that view is interested in more events of all kinds--
}
});
}
}
I hope I didn't misunderstand your question
but if what you want to do is to prevent multitoch on the image you can add this attribute
android:splitMotionEvents="false"
in the xml in the parent view of the imageview. for example :
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:splitMotionEvents="false"
>
// YOUR IMAGE VIEW HERE
</LinearLayout>
if you have any question feel free to ask in the comment :)
there are mainly three events on OnTouch action_down,Action_move and Action_up. do your coding on action down event i.e when user has touched your view. see the example here:
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction()==MotionEvent.ACTION_DOWN) {
//write your code here
}
else {
if (arg1.getAction()==MotionEvent.ACTION_MOVE){
do things
}
else {
if (arg1.getAction()==MotionEvent.ACTION_UP){
do things
}
}
}