我有两个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);
}
}
});
}
}
答案 0 :(得分:21)
您可以使用复合模式向TouchDelegate
添加多个View
。步骤进行:
TouchDelegateComposite
(无论您将哪个视图传递为
参数,它只用于获取上下文)TouchDelegates
并将其添加到复合根据推荐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 me和my 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
触摸区域(与推荐的“计算和添加像素”方法相比)由谷歌和其他人提供)。