如何使用多个 TouchDelegate

发布于 2024-11-26 08:06:03 字数 4167 浏览 1 评论 0原文

我有两个 ImageButtons,每个都位于relativelayout 内,这两个relativelayout 位于另一个relativelayout 中,我想为每个imagebutton 设置TouchDelegate。如果通常我将 TouchDelegate 添加到每个 ImageButton 并且它是父relativeLayout,那么只有一个 ImageButton 可以正常工作,另一个 ImageButton 不会扩展它的点击区域。因此,请帮助我了解如何在两个 ImageButtons 中使用 TouchDelegate。如果不可能,那么扩展视图的点击区域的有效方法是什么?预先感谢......

这是我的xml代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/FrameContainer"
android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout android:layout_alignParentLeft="true"
    android:id="@+id/relativeLayout3" android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout1" android:layout_width="113dip"
        android:layout_height="25dip">
        <ImageButton android:id="@+id/tutorial1"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial" />
    </RelativeLayout>
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout2" android:layout_width="113dip"
        android:layout_height="25dip" android:layout_marginLeft="100dip">
        <ImageButton android:id="@+id/tutorial2"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial"
            android:layout_marginLeft="50dip" />
    </RelativeLayout>
</RelativeLayout>

我的活动课程:

import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.TouchDelegate;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class TestTouchDelegate extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    View mParent1 = findViewById(R.id.relativeLayout1);
    mParent1.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds1 = new Rect();
            ImageButton mTutorialButton1 = (ImageButton) findViewById(R.id.tutorial1);
            mTutorialButton1.setEnabled(true);
            mTutorialButton1.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 1", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton1.getHitRect(bounds1);
            bounds1.right += 50;
            TouchDelegate touchDelegate1 = new TouchDelegate(bounds1, mTutorialButton1);

            if (View.class.isInstance(mTutorialButton1.getParent())) {
                ((View) mTutorialButton1.getParent()).setTouchDelegate(touchDelegate1);
            }
        }
    });

    //View mParent = findViewById(R.id.FrameContainer);
    View mParent2 = findViewById(R.id.relativeLayout2);
    mParent2.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds2 = new Rect();
            ImageButton mTutorialButton2 = (ImageButton) findViewById(R.id.tutorial2);
            mTutorialButton2.setEnabled(true);
            mTutorialButton2.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 2", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton2.getHitRect(bounds2);
            bounds2.left += 50;
            TouchDelegate touchDelegate2 = new TouchDelegate(bounds2, mTutorialButton2);

            if (View.class.isInstance(mTutorialButton2.getParent())) {
                ((View) mTutorialButton2.getParent()).setTouchDelegate(touchDelegate2);
            }
        }
    });

}

}

i have two ImageButtons, each inside a RelativeLayout and these two RelativeLayouts are in another RelativeLayout, i want to set TouchDelegate for each ImageButton. If normally i add TouchDelegate to each ImageButton and it's parent RelativeLayout then just one ImageButton works properly, Another one doesn't extend it's clicking area. So PLease help me on how to use TouchDelegate in both ImageButtons. If it's not possible then what can be a effective way to extend the clicking area of a view? Thanks in advance ........

Here is my xml code:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/FrameContainer"
android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout android:layout_alignParentLeft="true"
    android:id="@+id/relativeLayout3" android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout1" android:layout_width="113dip"
        android:layout_height="25dip">
        <ImageButton android:id="@+id/tutorial1"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial" />
    </RelativeLayout>
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout2" android:layout_width="113dip"
        android:layout_height="25dip" android:layout_marginLeft="100dip">
        <ImageButton android:id="@+id/tutorial2"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial"
            android:layout_marginLeft="50dip" />
    </RelativeLayout>
</RelativeLayout>

My Activity class :

import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.TouchDelegate;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class TestTouchDelegate extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    View mParent1 = findViewById(R.id.relativeLayout1);
    mParent1.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds1 = new Rect();
            ImageButton mTutorialButton1 = (ImageButton) findViewById(R.id.tutorial1);
            mTutorialButton1.setEnabled(true);
            mTutorialButton1.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 1", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton1.getHitRect(bounds1);
            bounds1.right += 50;
            TouchDelegate touchDelegate1 = new TouchDelegate(bounds1, mTutorialButton1);

            if (View.class.isInstance(mTutorialButton1.getParent())) {
                ((View) mTutorialButton1.getParent()).setTouchDelegate(touchDelegate1);
            }
        }
    });

    //View mParent = findViewById(R.id.FrameContainer);
    View mParent2 = findViewById(R.id.relativeLayout2);
    mParent2.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds2 = new Rect();
            ImageButton mTutorialButton2 = (ImageButton) findViewById(R.id.tutorial2);
            mTutorialButton2.setEnabled(true);
            mTutorialButton2.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 2", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton2.getHitRect(bounds2);
            bounds2.left += 50;
            TouchDelegate touchDelegate2 = new TouchDelegate(bounds2, mTutorialButton2);

            if (View.class.isInstance(mTutorialButton2.getParent())) {
                ((View) mTutorialButton2.getParent()).setTouchDelegate(touchDelegate2);
            }
        }
    });

}

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

口干舌燥 2024-12-03 08:06:04

每个视图应该只有一个触摸委托。 View 类中 getTouchDelegate() 的文档如下:

“获取此 View 的 TouchDelegate”。

只能有一个 TouchDelegate。要每个视图仅使用一个 TouchDelegate,您可以将每个可触摸视图包装在一个视图中,其尺寸反映您想要可触摸的内容。 说明如何仅使用一个静态方法对多个视图执行此操作 (http://www.youtube.com/watch?v=jF6Ad4GYjRU&t=37m4s)

  public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
bigView.post(new Runnable() {
    @Override
    public void run() {
        Rect rect = new Rect();
        smallView.getHitRect(rect);
        rect.top -= extraPadding;
        rect.left -= extraPadding;
        rect.right += extraPadding;
        rect.bottom += extraPadding;
        bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
    }
});

square 的一位 Android 开发人员给出了一个示例, 不想弄乱你的视图层次结构。我还能想到另外两个选择。您可以定义可触摸视图内可触摸内容的边界,并确保将所有触摸事件从相应的父视图传递到该子视图。或者您可以覆盖可触摸视图的 getHitRect() 。前者会很快使您的代码变得混乱并使其难以理解,因此后者是更好的前进方向。您想要重写 getHitRect。

其中 mPadding 是您希望在视图周围可触摸的额外区域的数量,您可以使用如下所示的内容:

    @Override
public void getHitRect(Rect outRect) {
    outRect.set(getLeft() - mPadding, getTop() - mPadding, getRight() + mPadding, getTop() + mPadding);
}

如果您使用类似上面的代码,您将必须考虑附近有哪些可触摸视图。堆栈最高的视图的可触摸区域可能会重叠在另一个视图的顶部。

另一个类似的选项是仅更改可触摸视图的填充。我不喜欢将此作为解决方案,因为跟踪视图大小的调整方式可能会变得困难。

There is only supposed to be one touch delegate for each view. The documentation for getTouchDelegate() in the View class reads:

"Gets the TouchDelegate for this View."

There is only to be one TouchDelegate. To use only one TouchDelegate per view, you can wrap each touchable view within a view with dimensions reflecting what you would like to be touchable. An android developer at square gives an example of how you can do this for multiple Views using just one static method (http://www.youtube.com/watch?v=jF6Ad4GYjRU&t=37m4s):

  public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
bigView.post(new Runnable() {
    @Override
    public void run() {
        Rect rect = new Rect();
        smallView.getHitRect(rect);
        rect.top -= extraPadding;
        rect.left -= extraPadding;
        rect.right += extraPadding;
        rect.bottom += extraPadding;
        bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
    }
});

}

Let's say that you do not want to clutter your view hierarchy. There are two other options I can think of. You can define the bounds of what is touchable inside the touchable view and make sure to pass all touchevents to that child view from respective parent views. Or you can override getHitRect() for the touchable view. The former will quickly clutter your code and make it difficult to understand, so the latter is the better way forward. You want to go with overriding getHitRect.

Where mPadding is the amount of extra area you want to be touchable around your view, you could use something like the following:

    @Override
public void getHitRect(Rect outRect) {
    outRect.set(getLeft() - mPadding, getTop() - mPadding, getRight() + mPadding, getTop() + mPadding);
}

If you use code like the above you'll have to consider what touchable views are nearby. The touchable area of the View that is highest on the stack could overlap on top of another View.

Another similar option would be to just change the padding of the touchable view. I dislike this as a solution because it can become difficult to keep track of how Views are being resized.

沦落红尘 2024-12-03 08:06:04

@need1milliondollars 答案的 Kotlin 版本:

class TouchDelegateComposite(view: View) : TouchDelegate(Rect(), view) {

    private val delegates: MutableList<TouchDelegate> = ArrayList()

    fun addDelegate(delegate: TouchDelegate) {
        delegates.add(delegate)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var res = false
        val x = event.x
        val y = event.y
        for (delegate in delegates) {
            event.setLocation(x, y)
            res = delegate.onTouchEvent(event) || res
        }
        return res
    }
}

Kotlin version of @need1milliondollars's answer:

class TouchDelegateComposite(view: View) : TouchDelegate(Rect(), view) {

    private val delegates: MutableList<TouchDelegate> = ArrayList()

    fun addDelegate(delegate: TouchDelegate) {
        delegates.add(delegate)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var res = false
        val x = event.x
        val y = event.y
        for (delegate in delegates) {
            event.setLocation(x, y)
            res = delegate.onTouchEvent(event) || res
        }
        return res
    }
}
梦醒灬来后我 2024-12-03 08:06:04

完全可用的 Kotlin 扩展功能,允许多个视图将其触摸目标增加相同的量:

// Example of usage
parentLayout.increaseHitAreaForViews(views = *arrayOf(myFirstView, mySecondView, myThirdView))

/*
 * Use this function if a parent view contains more than one view that
 * needs to increase its touch target hit area.
 *
 * Call this on the parent view
 */
fun View.increaseHitAreaForViews(@DimenRes radiusIncreaseDpRes: Int = R.dimen.touch_target_default_radius_increase, vararg views: View) {
    val increasedRadiusPixels = resources.getDimensionPixelSize(radiusIncreaseDpRes)
    val touchDelegateComposite = TouchDelegateComposite(this)
    post {
        views.forEach { view ->
            val rect = Rect()
            view.getHitRect(rect)
            rect.top -= increasedRadiusPixels
            rect.left -= increasedRadiusPixels
            rect.bottom += increasedRadiusPixels
            rect.right += increasedRadiusPixels
            touchDelegateComposite.addDelegate(TouchDelegate(rect, view))
        }
        touchDelegate = touchDelegateComposite
    }
}

class TouchDelegateComposite(view: View) : TouchDelegate(Rect(), view) {
    private val delegates = mutableListOf<TouchDelegate>()

    fun addDelegate(delegate: TouchDelegate) {
        delegates.add(delegate)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var res = false
        for (delegate in delegates) {
            event.setLocation(event.x, event.y)
            res = delegate.onTouchEvent(event) || res
        }
        return res
    }
}

Fully working Kotlin extension function which allows for multiple views to increase their touch target by the same amount:

// Example of usage
parentLayout.increaseHitAreaForViews(views = *arrayOf(myFirstView, mySecondView, myThirdView))

/*
 * Use this function if a parent view contains more than one view that
 * needs to increase its touch target hit area.
 *
 * Call this on the parent view
 */
fun View.increaseHitAreaForViews(@DimenRes radiusIncreaseDpRes: Int = R.dimen.touch_target_default_radius_increase, vararg views: View) {
    val increasedRadiusPixels = resources.getDimensionPixelSize(radiusIncreaseDpRes)
    val touchDelegateComposite = TouchDelegateComposite(this)
    post {
        views.forEach { view ->
            val rect = Rect()
            view.getHitRect(rect)
            rect.top -= increasedRadiusPixels
            rect.left -= increasedRadiusPixels
            rect.bottom += increasedRadiusPixels
            rect.right += increasedRadiusPixels
            touchDelegateComposite.addDelegate(TouchDelegate(rect, view))
        }
        touchDelegate = touchDelegateComposite
    }
}

class TouchDelegateComposite(view: View) : TouchDelegate(Rect(), view) {
    private val delegates = mutableListOf<TouchDelegate>()

    fun addDelegate(delegate: TouchDelegate) {
        delegates.add(delegate)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var res = false
        for (delegate in delegates) {
            event.setLocation(event.x, event.y)
            res = delegate.onTouchEvent(event) || res
        }
        return res
    }
}
时光无声 2024-12-03 08:06:04

为了使您的代码正常工作,您需要减少 bounds2 的左边框,而不是增加它。

bounds2.left -= 50;

在尝试了 TouchDelegate 之后,我找到了下面的代码,它在任何 Android 版本上都适用。诀窍是在调用布局后扩展保证的区域。

public class ViewUtils {

    public static final void extendTouchArea(final View view, 
            final int padding) {

        view.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {

            @Override
            @SuppressWarnings("deprecation")
            public void onGlobalLayout() {
                final Rect touchArea = new Rect();
                view.getHitRect(touchArea);
                touchArea.top -= padding;
                touchArea.bottom += padding;
                touchArea.left -= padding;
                touchArea.right += padding;

                final TouchDelegate touchDelegate = 
                    new TouchDelegate(touchArea, view);
                final View parent = (View) view.getParent();
                parent.setTouchDelegate(touchDelegate);

                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }

}

To make your code working you need to decrease left border of bounds2, and not increase it.

bounds2.left -= 50;

After playing around with TouchDelegate, I came to the code below, which works for me all the time on any Android version. The trick is to extend area guarantied after layout is called.

public class ViewUtils {

    public static final void extendTouchArea(final View view, 
            final int padding) {

        view.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {

            @Override
            @SuppressWarnings("deprecation")
            public void onGlobalLayout() {
                final Rect touchArea = new Rect();
                view.getHitRect(touchArea);
                touchArea.top -= padding;
                touchArea.bottom += padding;
                touchArea.left -= padding;
                touchArea.right += padding;

                final TouchDelegate touchDelegate = 
                    new TouchDelegate(touchArea, view);
                final View parent = (View) view.getParent();
                parent.setTouchDelegate(touchDelegate);

                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }

}
天涯离梦残月幽梦 2024-12-03 08:06:04

好吧,我想没有人提供真正的答案来解决它并使其变得容易。最近我遇到了同样的问题,阅读了上面的所有内容,我只是不知道如何让它工作。但最后我做到了。首先要记住的事情你的想法!让我们假设你有一个完整的布局,其中包含必须扩展的两个小按钮,因此你必须在主布局中创建另一个布局,并将另一个按钮放入新创建的布局中,因此在这种情况下使用静态方法你可以同时将触摸委托给 2 个按钮现在更深入地一步步进入代码!
首先,您肯定会找到像这样的 MAINLAYOUT 和按钮的视图。(此布局将保存我们的第一个按钮)

RelativeLayout mymainlayout = (RelativeLayout)findViewById(R.id.mymainlayout)
Button mybutoninthislayout = (Button)findViewById(R.id.mybutton)

好的,我们已经找到主布局及其按钮视图,它将保存所有内容,它只是我们的 onCreate()显示布局,但你必须找到以防万一以后使用它。好的,下一步是什么?我们在主布局中创建另一个RelativeLayout,其宽度和高度符合你的口味(这个新创建的布局将容纳我们的第二个按钮)

RelativeLayout myinnerlayout = (RelativeLayout)findViewById(R.id.myinnerlayout)
Button mybuttoninsideinnerlayout = (Button)findViewById(R.id.mysecondbutton)

好的我们完成了找到视图,这样我们现在就可以复制和粘贴首先回答您问题的人的代码。只需将该代码复制到您的主要活动中即可。

 public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
    bigView.post(new Runnable() {
        @Override
        public void run() {
            Rect rect = new Rect();
            smallView.getHitRect(rect);
            rect.top -= extraPadding;
            rect.left -= extraPadding;
            rect.right += extraPadding;
            rect.bottom += extraPadding;
            bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
        }
    });
}

现在如何使用这个方法并使其发挥作用?这就是方法!
在您的 onCreate() 方法上粘贴下一个代码片段

  expandTouchArea(mymainlayout,mybutoninthislayout,60);
  expandTouchArea(myinnerlayout, mybuttoninsideinnerlayout,60);

解释我们在此片段中所做的事情。我们采用了创建的名为 expandTouchArea() 的静态方法并提供了 3 个参数。
第一个参数 - 包含必须扩展区域的按钮的布局
第二个参数 - 扩展其区域的实际按钮
第三个参数 - 我们希望按钮区域扩大多少的区域(以像素为单位)!
享受!

Ok i guess nobody provides the real answer to make solution of it and make it easy.Lately i had same issue and reading all above i just had no clue how just to make it work.But finally i did it.First thing to keep in your mind!Lets pretend you have one whole layout which is holding your two small buttons which area must be expanded,so you MUST make another layout inside your main layout and put another button to your newly created layout so in that case with static method you can give touch delegate to 2 buttons at the same time.Now more deeply and step by step into code!
first you surely just find the view of your MAINLAYOUT and Button like this.(this layout will hold our first button)

RelativeLayout mymainlayout = (RelativeLayout)findViewById(R.id.mymainlayout)
Button mybutoninthislayout = (Button)findViewById(R.id.mybutton)

ok we done finding the main layout and its button view which will hold everything its just our onCreate() displaying layout but you have to find in case to use it later.Ok what next?We create another RelativeLayout inside our main layout which width and height is on your taste(this newly created layout will hold our second button)

RelativeLayout myinnerlayout = (RelativeLayout)findViewById(R.id.myinnerlayout)
Button mybuttoninsideinnerlayout = (Button)findViewById(R.id.mysecondbutton)

ok we done finding views so we can now just copy and paste the code of our guy who firstly answered your question.Just copy that code inside your main activity.

 public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
    bigView.post(new Runnable() {
        @Override
        public void run() {
            Rect rect = new Rect();
            smallView.getHitRect(rect);
            rect.top -= extraPadding;
            rect.left -= extraPadding;
            rect.right += extraPadding;
            rect.bottom += extraPadding;
            bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
        }
    });
}

Now how to use this method and make it work?here is how!
on your onCreate() method paste the next code snippet

  expandTouchArea(mymainlayout,mybutoninthislayout,60);
  expandTouchArea(myinnerlayout, mybuttoninsideinnerlayout,60);

Explanation on what we did in this snipped.We took our created static method named expandTouchArea() and gave 3 arguments.
1st argument-The layout which holds the button which area must be expanded
2nd argument - actual button to expand the area of it
3rd argument - the area in pixels of how much we want the button area to be expanded!
ENJOY!

爱殇璃 2024-12-03 08:06:04

我遇到了同样的问题:尝试为不同的 LinearLayouts 添加多个 TouchDelegates,在一个布局中将触摸事件分别路由到单独的 Switches。

有关详细信息,请参阅我提出的这个问题我的回答

我发现的是:一旦我分别用另一个LinearLayout包围LinearLayouts,第二个(每隔一个连续的结束)TouchDelegate 开始按预期工作。

因此,这可能有助于OP为他的问题创建一个可行的解决方案。
但对于它为什么会这样,我还没有一个令人满意的解释。

I had the same issue: Trying to add multiple TouchDelegates for different LinearLayouts that route touch events to separate Switches respectively, in one layout.

For details please refer to this question asked by me and my answer.

What I found is: Once I enclose the LinearLayouts each by another LinearLayout, respectively, the second (end every other successive) TouchDelegate starts to work as expected.

So this might help the OP to create a working solution to his problem.
I don't have a satisfying explanation on why it behaves like this, though.

寄与心 2024-12-03 08:06:04

我复制了 TouchDelegate 的代码并做了一些修改。现在它可以支持多个视图,无论这些视图是否有共同的父视图

class MyTouchDelegate: TouchDelegate {
private var mDelegateViews = ArrayList<View>()

private var mBoundses = ArrayList<Rect>()

private var mSlopBoundses = ArrayList<Rect>()

private var mDelegateTargeted: Boolean = false

val ABOVE = 1
val BELOW = 2
val TO_LEFT = 4
val TO_RIGHT = 8

private var mSlop: Int = 0

constructor(context: Context): super(Rect(), View(context)) {
    mSlop = ViewConfiguration.get(context).scaledTouchSlop
}

fun addTouchDelegate(delegateView: View, bounds: Rect) {
    val slopBounds = Rect(bounds)
    slopBounds.inset(-mSlop, -mSlop)
    mDelegateViews.add(delegateView)
    mSlopBoundses.add(slopBounds)
    mBoundses.add(Rect(bounds))
}

var targetIndex = -1

override fun onTouchEvent(event: MotionEvent): Boolean {
    val x = event.x.toInt()
    val y = event.y.toInt()
    var sendToDelegate = false
    var hit = true
    var handled = false


    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            targetIndex = -1

            for ((index, item) in mBoundses.withIndex()) {
                if (item.contains(x, y)) {
                    mDelegateTargeted = true
                    targetIndex = index
                    sendToDelegate = true
                }
            }
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> {
            sendToDelegate = mDelegateTargeted
            if (sendToDelegate) {
                val slopBounds = mSlopBoundses[targetIndex]
                if (!slopBounds.contains(x, y)) {
                    hit = false
                }
            }
        }
        MotionEvent.ACTION_CANCEL -> {
            sendToDelegate = mDelegateTargeted
            mDelegateTargeted = false
        }
    }
    if (sendToDelegate) {
        val delegateView = mDelegateViews[targetIndex]

        if (hit) {
            // Offset event coordinates to be inside the target view
            event.setLocation((delegateView.width / 2).toFloat(), (delegateView.height / 2).toFloat())
        } else {
            // Offset event coordinates to be outside the target view (in case it does
            // something like tracking pressed state)
            val slop = mSlop
            event.setLocation((-(slop * 2)).toFloat(), (-(slop * 2)).toFloat())
        }
        handled = delegateView.dispatchTouchEvent(event)
    }
    return handled
}

}

像这样使用它

  fun expandTouchArea(viewList: List<View>, touchSlop: Int) {


    val rect = Rect()

    viewList.forEach {
        it.getHitRect(rect)
        if (rect.left == rect.right && rect.top == rect.bottom) {
            postDelay(Runnable { expandTouchArea(viewList, touchSlop) }, 200)
            return
        }
        rect.top -= touchSlop
        rect.left -= touchSlop
        rect.right += touchSlop
        rect.bottom += touchSlop

        val parent = it.parent as? View
        if (parent != null) {
            val parentDelegate = parent.touchDelegate
            if (parentDelegate != null) {
                (parentDelegate as? MyTouchDelegate)?.addTouchDelegate(it, rect)
            } else {
                val touchDelegate = MyTouchDelegate(this)
                touchDelegate.addTouchDelegate(it, rect)
                parent.touchDelegate = touchDelegate
            }
        }
    }
}

I copy the code of TouchDelegate and made some alter. Now it can support multi Views regardless of whether thoese views had common parents

class MyTouchDelegate: TouchDelegate {
private var mDelegateViews = ArrayList<View>()

private var mBoundses = ArrayList<Rect>()

private var mSlopBoundses = ArrayList<Rect>()

private var mDelegateTargeted: Boolean = false

val ABOVE = 1
val BELOW = 2
val TO_LEFT = 4
val TO_RIGHT = 8

private var mSlop: Int = 0

constructor(context: Context): super(Rect(), View(context)) {
    mSlop = ViewConfiguration.get(context).scaledTouchSlop
}

fun addTouchDelegate(delegateView: View, bounds: Rect) {
    val slopBounds = Rect(bounds)
    slopBounds.inset(-mSlop, -mSlop)
    mDelegateViews.add(delegateView)
    mSlopBoundses.add(slopBounds)
    mBoundses.add(Rect(bounds))
}

var targetIndex = -1

override fun onTouchEvent(event: MotionEvent): Boolean {
    val x = event.x.toInt()
    val y = event.y.toInt()
    var sendToDelegate = false
    var hit = true
    var handled = false


    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            targetIndex = -1

            for ((index, item) in mBoundses.withIndex()) {
                if (item.contains(x, y)) {
                    mDelegateTargeted = true
                    targetIndex = index
                    sendToDelegate = true
                }
            }
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> {
            sendToDelegate = mDelegateTargeted
            if (sendToDelegate) {
                val slopBounds = mSlopBoundses[targetIndex]
                if (!slopBounds.contains(x, y)) {
                    hit = false
                }
            }
        }
        MotionEvent.ACTION_CANCEL -> {
            sendToDelegate = mDelegateTargeted
            mDelegateTargeted = false
        }
    }
    if (sendToDelegate) {
        val delegateView = mDelegateViews[targetIndex]

        if (hit) {
            // Offset event coordinates to be inside the target view
            event.setLocation((delegateView.width / 2).toFloat(), (delegateView.height / 2).toFloat())
        } else {
            // Offset event coordinates to be outside the target view (in case it does
            // something like tracking pressed state)
            val slop = mSlop
            event.setLocation((-(slop * 2)).toFloat(), (-(slop * 2)).toFloat())
        }
        handled = delegateView.dispatchTouchEvent(event)
    }
    return handled
}

}

use it like this

  fun expandTouchArea(viewList: List<View>, touchSlop: Int) {


    val rect = Rect()

    viewList.forEach {
        it.getHitRect(rect)
        if (rect.left == rect.right && rect.top == rect.bottom) {
            postDelay(Runnable { expandTouchArea(viewList, touchSlop) }, 200)
            return
        }
        rect.top -= touchSlop
        rect.left -= touchSlop
        rect.right += touchSlop
        rect.bottom += touchSlop

        val parent = it.parent as? View
        if (parent != null) {
            val parentDelegate = parent.touchDelegate
            if (parentDelegate != null) {
                (parentDelegate as? MyTouchDelegate)?.addTouchDelegate(it, rect)
            } else {
                val touchDelegate = MyTouchDelegate(this)
                touchDelegate.addTouchDelegate(it, rect)
                parent.touchDelegate = touchDelegate
            }
        }
    }
}
失去的东西太少 2024-12-03 08:06:04

我从 Brendan Weinstein 上面列出的链接实现了一个简单的解决方案 - 与所有其他解决方案相比,静态方法非常干净整洁。填充对我来说根本不起作用。

我的用例是增加 2 个小按钮的触摸面积以改善用户体验。

我有一个 MyUserInterfaceManager 类,我在其中插入 youtube 视频< /a>;

public static void changeViewsTouchArea(final View newTouchArea, 
                                        final View viewToChange) {
    newTouchArea.post(new Runnable() {
        @Override
        public void run() {
            Rect rect = new Rect(0,0, newTouchArea.getWidth(), newTouchArea.getHeight());       
            newTouchArea.setTouchDelegate(new TouchDelegate(rect, viewToChange));
        }
    });
}

然后在 XML 中我有以下代码(每个图像按钮);

<!-- constrain touch area to button / view!-->
<FrameLayout
     android:id="@+id/btn_a_touch_area"
     android:layout_width="@dimen/large_touch_area"
     android:layout_height="@dimen/large_touch_area"
/>

<ImageButton
     android:id="@+id/btn_id_here"
     android:layout_width="@dimen/button_size"
     android:layout_height="@dimen/button_size" />

在我的片段的 onCreateView(...) 方法中,我调用了每个需要修改触摸区域的 View 的方法(在绑定两个 Views 之后) >!);

MyUserInterfaceManager.changeViewsTouchArea(buttonATouchArea, buttonA);

buttonA.setOnClickListener(v -> {
   // add event code here
});

这个解决方案意味着我可以明确地设计和查看布局文件中的触摸区域 - 必须确保我不会太靠近其他View触摸区域(与推荐的“计算和添加像素”方法相比)由谷歌和其他人提供)。

I implemented a simple solution from the link that Brendan Weinstein listed above - the static method was incredibly clean and tidy compared to all other solutions. Padding for me simply doesnt work.

My use case was increasing the touch area of 2 small buttons to improve UX.

I have a MyUserInterfaceManager class, where i insert the static function from the youtube video;

public static void changeViewsTouchArea(final View newTouchArea, 
                                        final View viewToChange) {
    newTouchArea.post(new Runnable() {
        @Override
        public void run() {
            Rect rect = new Rect(0,0, newTouchArea.getWidth(), newTouchArea.getHeight());       
            newTouchArea.setTouchDelegate(new TouchDelegate(rect, viewToChange));
        }
    });
}

Then in XML i have the following code (per imagebutton);

<!-- constrain touch area to button / view!-->
<FrameLayout
     android:id="@+id/btn_a_touch_area"
     android:layout_width="@dimen/large_touch_area"
     android:layout_height="@dimen/large_touch_area"
/>

<ImageButton
     android:id="@+id/btn_id_here"
     android:layout_width="@dimen/button_size"
     android:layout_height="@dimen/button_size" />

The in the onCreateView(...) method of my fragment i called the method per View that needs the touch area modified (after binding both Views!);

MyUserInterfaceManager.changeViewsTouchArea(buttonATouchArea, buttonA);

buttonA.setOnClickListener(v -> {
   // add event code here
});

This solution means i can explicitly design and see the touch area in layout files - a must have to ensure im not getting too close to other View touch areas (compared to the "calculate and add pixels" methods recommended by google and others).

忘东忘西忘不掉你 2024-12-03 08:06:03

您可以使用复合模式向 View 添加多个 TouchDelegate。步骤:

  1. 创建 TouchDelegateComposite (无论您将作为
    参数,它仅用于获取上下文)
  2. 创建必要的 TouchDelegates 并将它们添加到复合
  3. 按照建议添加复合以查看此处(通过view.post(new Runnable)

    public class TouchDelegateComposite 扩展 TouchDelegate {
    
        私有最终列表 delegates = new ArrayList();
        私有静态最终矩形空矩形=新矩形();
    
        公共 TouchDelegateComposite(查看视图){
            超级(空矩形,视图);
        }
    
        公共无效addDelegate(TouchDelegate委托){
            if (委托!= null) {
                delegates.add(委托);
            }
        }
    
        @覆盖
        公共布尔onTouchEvent(MotionEvent事件){
            布尔值=假;
            浮动x = event.getX();
            float y = event.getY();
            for (TouchDelegate 委托:委托) {
                event.setLocation(x, y);
                res = delegate.onTouchEvent(事件) ||资源;
            }
            返回资源;
        }
    
    }
    

You can use composite pattern to be able to add more than one TouchDelegate to the View. Steps:

  1. Create TouchDelegateComposite (no matter what view you'll pass as an
    argument, it's used just to get the Context)
  2. Create necessary TouchDelegates and add them to composite
  3. Add composite to view as they recommend here (via view.post(new Runnable))

    public class TouchDelegateComposite extends TouchDelegate {
    
        private final List<TouchDelegate> delegates = new ArrayList<TouchDelegate>();
        private static final Rect emptyRect = new Rect();
    
        public TouchDelegateComposite(View view) {
            super(emptyRect, view);
        }
    
        public void addDelegate(TouchDelegate delegate) {
            if (delegate != null) {
                delegates.add(delegate);
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            boolean res = false;
            float x = event.getX();
            float y = event.getY();
            for (TouchDelegate delegate : delegates) {
                event.setLocation(x, y);
                res = delegate.onTouchEvent(event) || res;
            }
            return res;
        }
    
    }
    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文