AndroidでTouchDelegateを使用して、ビューのクリックターゲットのサイズを大きくする方法の例はありますか?


82

私の理解では、ビューが小さすぎて簡単に触れることができない場合は、TouchDelegateを使用して、そのビューのクリック可能な領域を増やすことになっています。

ただし、Googleで使用例を検索すると、複数の人が質問をしますが、回答はほとんどありません。

ビューのタッチデリゲートを設定して、クリック可能な領域をすべての方向に4ピクセル増やすなど、適切な方法を知っている人はいますか?


3
TouchDelegateの使用方法に関する追加の良い投稿があります:groups.google.com/group/android-developers/browse_thread/thread/…–
Mason Lee

回答:


100

Googleの友人に聞いたところ、TouchDelegateの使い方を理解するのを手伝ってくれました。これが私たちが思いついたものです:

final View parent = (View) delegate.getParent();
parent.post( new Runnable() {
    // Post in the parent's message queue to make sure the parent
    // lays out its children before we call getHitRect()
    public void run() {
        final Rect r = new Rect();
        delegate.getHitRect(r);
        r.top -= 4;
        r.bottom += 4;
        parent.setTouchDelegate( new TouchDelegate( r , delegate));
    }
});

16
これを実行したいボタンが1つの画面に2つある場合はどうなりますか?もう一度setTouchDelegateを設定すると、前のタッチデリゲートが消去されます。
cottonBallPaws

2
これは私には機能しませんでした...デリゲートは、TouchDelegateを追加しようとしているビューであると思います。
ケビンパーカー

20
@AmokraneChentir同じ問題が発生しました。TouchDelegatesを使用する代わりに、ボタンを拡張するクラスがあります。このクラスでは、getHitRectをオーバーライドします。ヒットした長方形を親ビューのサイズまで拡張できます。public void getHitRect(Rect outRect){outRect.set(getLeft()-mPadding、getTop()-mPadding、getRight()+ mPadding、getTop()+ mPadding); }
Brendan Weinstein

5
@Amokrane:私が提供したソリューションはICSでは機能しないことがわかりました。私は2つの異なるタッチ委任戦略の上でブログ記事を書いたbrendanweinstein.me/2012/06/26/...、あなたは、両方の戦略のためのサンプルコードを入手することができますgithub.com/brendanw/Touch-Delegates/blob/master/src/com/ brendan /…
Brendan Weinstein

8
@ZsoltSafranyによると、これを行うためのより安全な方法は、OnGlobalLayoutListenerを使用し、デリゲートonGlobalLayout()を割り当てることです。
greg7gkb 2013

20

これは、主にこのブログ投稿から描画した1つの画面に複数のビュー(チ​​ェックボックス)を表示することで実現できました。基本的に、emmbyのソリューションを使用して、各ボタンとその親に個別に適用します。

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

私の場合、チェックボックスが上にオーバーレイされたimageviewsのグリッドビューがあり、次のようにメソッドを呼び出しました。

CheckBox mCheckBox = (CheckBox) convertView.findViewById(R.id.checkBox1);
final ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView1);

// Increase checkbox clickable area
expandTouchArea(imageView, mCheckBox, 100);

私にとって素晴らしい仕事をしています。


11

このソリューションは、@ BrendanWeinsteinによってコメントで投稿されました。

を送信する代わりに、のメソッドをTouchDelegateオーバーライドできます(拡張する場合)。getHitRect(Rect)View

public class MyView extends View {  //NOTE: any other View can be used here

    /* a lot of required methods */

    @Override
    public void getHitRect(Rect r) {
        super.getHitRect(r); //get hit Rect of current View

        if(r == null) {
            return;
        }

        /* Manipulate with rect as you wish */
        r.top -= 10;
    }
}

面白いアイデア。しかし、レイアウトからビューを作成できないということではないでしょうか。いいえ、待ってください—新しいビュークラスを設計し、それをレイアウトで使用する場合。それはうまくいくかもしれません。試すだけの価値があります。
マーティン

10
@Martinそれは正しい考えです。残念ながら、getHitRectは、ICSの時点でdispatchTouchEventによって呼び出されなくなりました。grepcode.com/ file / repository.grepcode.com / java / ext /…getHitRectのオーバーライドは、ジンジャーブレッド以下でのみ機能します。
Brendan Weinstein 2014

6

emmbyのアプローチは私にはうまくいきませんでしたが、少し変更した後、うまくいきました。

private void initApplyButtonOnClick() {
    mApplyButton.setOnClickListener(onApplyClickListener);
    final View parent = (View)mApplyButton.getParent();
    parent.post(new Runnable() {

        @Override
        public void run() {
            final Rect hitRect = new Rect();
            parent.getHitRect(hitRect);
            hitRect.right = hitRect.right - hitRect.left;
            hitRect.bottom = hitRect.bottom - hitRect.top;
            hitRect.top = 0;
            hitRect.left = 0;
            parent.setTouchDelegate(new TouchDelegate(hitRect , mApplyButton));         
        }
    });
}

多分それは誰かの時間を節約することができます


これは私のために働いたものです。実際には、親全体がクリックイベントを子に送信します。直接の親である必要はありません(親の親になることができます)。
Androidの開発者

委任するビューが1つある場合は、おそらくこれで機能します。たとえば、タッチエリアを拡張するために50のビューがある場合はどうなりますか?—このスクリーンショットのように:plus.google.com/u/0/b/112127498414857833804/…— TouchDelegateはそれでも適切なソリューションですか?
マーティン

2

@Mason Leeのコメントによると、これで私の問題は解決しました。私のプロジェクトには、相対的なレイアウトと1つのボタンがありました。つまり、親は->レイアウトであり、子は->ボタンです。

これがグーグルリンクの例グーグルコードです

彼の非常に貴重な答えを削除する場合、私は彼の答えをここに置きました。

最近、TouchDelegateの使用方法について尋ねられました。私はこれについて少し錆びていて、それに関する良いドキュメントを見つけることができませんでした。これが私が少し試行錯誤した後に書いたコードです。touch_delegate_viewは、IDがtouch_delegate_rootの単純なRelativeLayoutです。レイアウトの単一の子であるボタンdelegated_buttonを使用して定義しました。この例では、ボタンのクリック可能な領域をボタンの上部から200ピクセル上に拡張します。

public class TouchDelegateSample extends Activity {

  Button mButton;   
  @Override   
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.touch_delegate_view);
    mButton = (Button)findViewById(R.id.delegated_button);
    View parent = findViewById(R.id.touch_delegate_root);

    // post a runnable to the parent view's message queue so its run after
    // the view is drawn
    parent.post(new Runnable() {
      @Override
      public void run() {
        Rect delegateArea = new Rect();
        Button delegate = TouchDelegateSample.this.mButton;
        delegate.getHitRect(delegateArea);
        delegateArea.top -= 200;
        TouchDelegate expandedArea = new TouchDelegate(delegateArea, delegate);
        // give the delegate to an ancestor of the view we're delegating the
        // area to
        if (View.class.isInstance(delegate.getParent())) {
          ((View)delegate.getParent()).setTouchDelegate(expandedArea);
        }
      }
    });   
  } 
}

乾杯、ジャスティンAndroidチーム@ Google

私からのいくつかの言葉:左側を拡張したい場合はマイナスで値を与え、オブジェクトの右側を拡張したい場合はプラスで値を与えます。これは、上部と下部で同じように機能します。


ここでの魔法のヒントは1つのボタンのようです—どういうわけか、TouchDelegateは複数のボタンには使用できないと感じています。確認できますか?
マーティン

1

その特定のコンポーネント(ボタン)にパディングを与えるのは良い考えではありませんか。


1
それはより大きく見えるようになります。ボタンの上にクリックできないテキストテキストがあり、そのテキストの領域を使用してボタンのクリック精度を向上させたい場合はどうでしょうか。-このスクリーンショットのように:plus.google.com/u/0/b/112127498414857833804/...
マーティン

1

パーティーに少し遅れましたが、多くの調査の後、私は現在使用しています:

/**
 * Expand the given child View's touchable area by the given padding, by
 * setting a TouchDelegate on the given ancestor View whenever its layout
 * changes.
 */*emphasized text*
public static void expandTouchArea(final View ancestorView,
    final View childView, final Rect padding) {

    ancestorView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                TouchDelegate delegate = null;

                if (childView.isShown()) {
                    // Get hitRect in parent's coordinates
                    Rect hitRect = new Rect();
                    childView.getHitRect(hitRect);

                    // Translate to ancestor's coordinates
                    int ancestorLoc[] = new int[2];
                    ancestorView.getLocationInWindow(ancestorLoc);

                    int parentLoc[] = new int[2];
                    ((View)childView.getParent()).getLocationInWindow(
                        parentLoc);

                    int xOffset = parentLoc[0] - ancestorLoc[0];
                    hitRect.left += xOffset;
                    hitRect.right += xOffset;

                    int yOffset = parentLoc[1] - ancestorLoc[1];
                    hitRect.top += yOffset;
                    hitRect.bottom += yOffset;

                    // Add padding
                    hitRect.top -= padding.top;
                    hitRect.bottom += padding.bottom;
                    hitRect.left -= padding.left;
                    hitRect.right += padding.right;

                    delegate = new TouchDelegate(hitRect, childView);
                }

                ancestorView.setTouchDelegate(delegate);
            }
        });
}

これは、親ビューだけでなく、任意の祖先ビューにTouchDelegateを設定できるため、受け入れられているソリューションよりも優れています。

受け入れられているソリューションとは異なり、祖先ビューのレイアウトが変更されるたびにTouchDelegateも更新されます。


0

プログラムでそれを行いたくない場合は、ボタン(ビュー)の背景として画像を使用している場合は、画像の周囲に透明な領域を作成するだけです。

灰色の領域を透明にして、タッチ領域を増やすことができます。

ここに画像の説明を入力してください


1
それでは、周りに空きスペースができて、他の用途に使用できないビューが表示されませんか?情報テキストを表示してボタンを閉じ、テキスト領域を使用してボタンのクリック領域を拡張する場合はどうなりますか?
マーティン

私はそれが最善で最後のアプローチだとは言いません。ただし、小さなボタンの画像を表示したいがタッチ領域を拡張したいという多くのシナリオに適合します。あなたの場合、他のビュー(テキストビュー)をクリックしたときにボタンを優先することは困難です。それはすべて一緒になって別の課題です。
Vinayak Bevinakatti 2015年

0

ほとんどの場合、より大きなタッチ領域を必要とするビューを別のヘッドレスビュー(人工透明ビュー)でラップし、ラッパービューにパディング/マージンを追加し、元のビューの代わりにラッパービューにもクリック/タッチをアタッチできます。より大きなタッチエリアが必要です。


それでは、周りに空きスペースができて、他の用途に使用できないビューが表示されませんか?情報テキストを表示してボタンを閉じ、テキスト領域を使用してボタンのクリック領域を拡張する場合はどうなりますか?
マーティン

@マーティン、それはあなたがそれをする方法に依存します。そして、やりたいことは何でも簡単にできるはずです。これらすべてのビューの上に透明なビューで好きなもの(ただし、必要なビューの数)をラップし、その領域にもクリックを割り当てます。
inteist 2013

0

タッチエリアを一般的に拡張するには、ほとんど制限がありません。次のコードを使用します。

これにより、指定さviewれたancestorビュー内の指定されたタッチ領域を指定さexpansionれたピクセル単位で拡張できます。指定されたビューが祖先レイアウトツリーにある限り、任意の祖先を選択できます。

    public static void expandTouchArea(final View view, final ViewGroup ancestor, final int expansion) {
    ancestor.post(new Runnable() {
        public void run() {
            Rect bounds = getRelativeBounds(view, ancestor);
            Rect expandedBounds = expand(bounds, expansion);
            // LOG.debug("Expanding touch area of {} within {} from {} by {}px to {}", view, ancestor, bounds, expansion, expandedBounds);
            ancestor.setTouchDelegate(new TouchDelegate(expandedBounds, view));
        }

        private Rect getRelativeBounds(View view, ViewGroup ancestor) {
            Point relativeLocation = getRelativeLocation(view, ancestor);
            return new Rect(relativeLocation.x, relativeLocation.y,
                            relativeLocation.x + view.getWidth(),
                            relativeLocation.y + view.getHeight());
        }

        private Point getRelativeLocation(View view, ViewGroup ancestor) {
            Point absoluteAncestorLocation = getAbsoluteLocation(ancestor);
            Point absoluteViewLocation = getAbsoluteLocation(view);
            return new Point(absoluteViewLocation.x - absoluteAncestorLocation.x,
                             absoluteViewLocation.y - absoluteAncestorLocation.y);
        }

        private Point getAbsoluteLocation(View view) {
            int[] absoluteLocation = new int[2];
            view.getLocationOnScreen(absoluteLocation);
            return new Point(absoluteLocation[0], absoluteLocation[1]);
        }

        private Rect expand(Rect rect, int by) {
            Rect expandedRect = new Rect(rect);
            expandedRect.left -= by;
            expandedRect.top -= by;
            expandedRect.right += by;
            expandedRect.bottom += by;
            return expandedRect;
        }
    });
}

適用される制限:

  • ビューに転送するには、祖先がタッチイベントをキャッチできる必要があるため、タッチ領域はビューの祖先の境界を超えることはできません。
  • TouchDelegateに設定できるのは1つだけViewGroupです。複数のタッチデリゲートを操作する場合は、別の祖先を選択するか、「複数のTouchDelegateの使用方法」で説明されているように構成するタッチデリゲートを使用します。

0

TouchDelegateの長方形の新しいサイズを取得するためだけにレイアウトパスを待つというアイデアが気に入らなかったため、別の解決策を選びました。

public class TouchSizeIncreaser extends FrameLayout {
    public TouchSizeIncreaser(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final View child = getChildAt(0);
        if(child != null) {
            child.onTouchEvent(event);
        }
        return true;
    }
}

そして、レイアウトで:

<ch.tutti.ui.util.TouchSizeIncreaser
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</ch.tutti.ui.util.TouchSizeIncreaser>

アイデアは、TouchSizeIncreaser FrameLayoutがスピナー(任意の子ビューである可能性があります)をラップし、ヒット長方形でキャプチャされたすべてのタッチイベントを子ビューに転送するというものです。クリックに対して機能し、範囲外でクリックしてもスピナーが開きます。他のより複雑なケースにどのような影響があるかはわかりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.