如何使用多个TouchDelegate

时间:2011-07-23 07:51:07

标签: android layout view imagebutton

我有两个ImageButtons,每个都在RelativeLayout中,这两个RelativeLayout在另一个RelativeLayout中,我想为每个ImageButton设置TouchDelegate。如果通常我将TouchDelegate添加到每个ImageButton并且它的父RelativeLayout然后只有一个ImageButton正常工作,另一个不扩展它的点击区域。因此,PLease帮助我了解如何在两个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);
            }
        }
    });

}

}

10 个答案:

答案 0 :(得分:21)

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

  1. 创建TouchDelegateComposite(无论您将哪个视图传递为 参数,它只用于获取上下文)
  2. 创建必要的TouchDelegates并将其添加到复合
  3. 根据推荐here(通过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;
        }
    
    }
    

答案 1 :(得分:3)

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

“获取此视图的TouchDelegate。”

只有一个TouchDelegate。要在每个视图中仅使用一个TouchDelegate,您可以在视图中包装每个可触摸视图,其尺寸反映了您想要触摸的内容。广场上的Android开发人员举例说明了如何使用一种静态方法为多个视图执行此操作(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));
    }
});

}

假设您不希望混淆视图层次结构。我能想到另外两个选择。您可以定义可触摸视图内可触摸内容的边界,并确保将所有touchevents从各个父视图传递到该子视图。或者,您可以为可触摸视图覆盖getHitRect()。前者会很快混乱你的代码并使其难以理解,因此后者是更好的前进方式。你想要重写getHitRect。

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

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

如果您使用上述代码,则必须考虑附近有哪些可触摸视图。堆栈中最高的View可触摸区域可能会在另一个View上重叠。

另一个类似的选择是只更改可触摸视图的填充。我不喜欢这个解决方案,因为很难跟踪视图的大小调整。

答案 2 :(得分:1)

要使代码正常工作,您需要减少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);
            }
        });
    }

}

答案 3 :(得分:0)

答案 4 :(得分:0)

好吧我猜没有人提供真正的答案来解决它并让它变得简单。最后我有同样的问题和阅读以上所有我只是不知道如何使其工作。但最后我做到了。第一件事保持在你的脑海里!让我假装你有一个完整的布局,拿着你的两个小按钮,哪个区域必须扩展,所以你必须在你的主布局内做另一个布局,并在你新创建的布局上放置另一个按钮,所以在这种情况下静态方法你可以同时给2个按钮触摸委托。现在更深入,一步步进入代码! 首先你肯定只是找到你的MAINLAYOUT和Button的视图。(这个布局将保留我们的第一个按钮)

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

解释我们在这个snipped中做了什么。我们使用我们创建的名为expandTouchArea()的静态方法并给出了3个参数。 第一个参数 - 保持按钮哪个区域必须扩展的布局 第二个参数 - 扩展它的区域的实际按钮 第三个参数 - 我们希望按钮区域扩展多少的区域(以像素为单位)! 享受!

答案 5 :(得分:0)

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

有关详细信息,请参阅this question asked by memy answer

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

所以这可能有助于OP为他的问题创建一个可行的解决方案。 尽管如此,我对它的行为原因并不满意。

答案 6 :(得分:0)

我复制了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
            }
        }
    }
}

答案 7 :(得分:0)

@ need1milliondollars答案的科特琳版:

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

答案 8 :(得分:0)

完全有效的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
    }
}

答案 9 :(得分:0)

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

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

我有一个 MyUserInterfaceManager 类,我在其中插入了 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));
        }
    });
}

然后在 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 触摸区域(与推荐的“计算和添加像素”方法相比)由谷歌和其他人提供)。