アイコン画像をドラッグしてごみ箱に入れるなどの、ドラッグをAndroidでやる場合にonTouchを使います。タッチした指の動きに合わせてlayoutメソッドを使い画像などを移動させ、あたかもドラッグしているようにできます。
2024.1.1
画像をドラッグさせる
画像をドラッグさせるために使う仕組みは大きく以下の2つを使います
1. layout(int, int, int, int)
- Viewのメソッドで、上下左右の位置を変えることでViewを移動
2. onTouch
- ムーブアクションを取得してそれと同じ座標に画像を配置
layout メソッドを使って画像を移動
例えば、このような画像bag.pngを用意してdrawableに配置します。
あるいは \res\mipmap 以下にあるDroid君のアイコン画像をコピーして代用もできます。
Viewクラスのメソッドであるlayoutは、引数としてViewの left, top, right, bottom を設定できます。
1 2 3 4 5 |
layout(int l, int t, int r, int b) // int l: Left position, relative to parent // int t: Top position, relative to parent // int r: Right position, relative to parent // int b: Bottom position, relative to parent |
View自体のサイズを固定とするとx方向、y方向の移動量と画像の縦横幅を加算することでViewを移動させられます。
1 2 3 4 5 |
// x: x方向の移動量 // y: y方向の移動量 // width: viewの横幅 // height: viewの高さ view.layout( x, y, x+width, y+height); |
これをImageViewに当てはめて画像を移動することを確認してみます。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
//package com.example.testimageviewdrag; import android.os.Bundle; import android.widget.Button; import android.widget.ImageView; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { private int counter = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); ImageView imageView = findViewById(R.id.image_view); imageView.layout(0, 0, imageView.getWidth(), imageView.getHeight()); Button button = findViewById(R.id.button); button.setOnClickListener( v -> { counter +=100; // x方向300pix固定、y方向は20pixづつ増加、画像の横縦幅はそのまま維持 int left = counter/2; int top = counter; int right = left + imageView.getWidth(); int bottom = top + imageView.getHeight(); imageView.layout(left, top, right, bottom); }); } } |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.571" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.081" /> <ImageView android:id="@+id/image_view" android:src="@drawable/bag" android:contentDescription="@string/description" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0" /> </androidx.constraintlayout.widget.ConstraintLayout> |
リソースです
strings.xml
1 2 3 4 5 |
<resources> <string name="app_name">Your App Name</string> <string name="button">Button</string> <string name="description">bag</string> </resources> |
ボタンを押すたびに少しづつ画像が斜め下に移動していきます。
Buttonタップを使って画像の移動ができるようになりました。
次は画像をつかんでドラッグをタッチイベントを使って実装します。
onTouchでタッチイベントを拾う
タッチイベントを扱うものとして
1 |
onTouch(View v, MotionEvent event) |
があります
似たものとしては
onTouchEvent(MotionEvent motionEvent)
がありますが、これはActivityの画面全体でのタッチイベントを拾う時に使います
Activity にOnTouchListener を実装しますので
1 |
public boolean onTouch(View v, MotionEvent event) {} |
の設定が必要です
一方onTouchは例えばImageViewにリスナーを設定します
1 2 |
// 画像にリスナーをセット imageView.setOnTouchListener(this); |
ところがこれはビルドエラーにはなりませんが警告が出てきます。
(このImageViewにリスナーを設定でできない事はないのですが…)
“Custom View has onTouchListner called on it but doesn’t override performClick”
performClickをoverrideしないといけないという警告です。これは簡単にはできないのでImageViewを継承したカスタムのCustomImageViewクラスを作ってoverrideします。
1 2 3 4 |
CustomImageView cImageView; ... // カスタムImageViewにリスナーをセット cImageView.setOnTouchListener(this); |
onTouch(View v, MotionEvent event):
Viewのタッチにより以下の状態を取得できます。
- ACTION_DOWN
- Viewタッチを検出
- ACTION_MOVE
- ドラッグ
- ACTION_UP
- Viewタッチが終了
ここでタッチした位置とドラッグしていく位置を getRawX(), getRawY() で取得して
その移動量をViewにのlayoutを使って反映させることで、Viewがつかまれてドラッグされたようにしていきます。
サンプルコード
CustomImageViewクラスをImageViewを継承して作り、そのインスタンスにonTuchイベントのリスナーを実装します。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
//package com.example.testimageviewdrag; import android.os.Bundle; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnTouchListener { private CustomImageView cImageView; private int preDx, preDy; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); textView = findViewById(R.id.text_view); cImageView = this.findViewById(R.id.image_view); cImageView.setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { // x,y 位置取得 int newDx = (int)event.getRawX(); int newDy = (int)event.getRawY(); switch (event.getAction()) { // タッチダウンでdragされた case MotionEvent.ACTION_MOVE: // ACTION_MOVEでの位置 // performCheckを入れろと警告が出るので v.performClick(); int dx = cImageView.getLeft() + (newDx - preDx); int dy = cImageView.getTop() + (newDy - preDy); int imgW = dx + cImageView.getWidth(); int imgH = dy + cImageView.getHeight(); // 画像の位置を設定する cImageView.layout(dx, dy, imgW, imgH); String str = "dx="+dx+"\ndy="+dy; textView.setText(str); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30); Log.d("onTouch","ACTION_MOVE: dx="+dx+", dy="+dy); break; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: // nothing to do break; default: break; } // タッチした位置を古い位置とする preDx = newDx; preDy = newDy; return true; } } |
ImageViewを継承させるのですが、
1 |
androidx.appcompat.widget.AppCompatImageView; |
を使うように推奨されますのでAppCompatImageViewを継承して、念願のperformClick()をoverrideします。新しくCustomImageView.javaのファイルを作成
CustomImageView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//package com.example.testimageviewdrag; import androidx.appcompat.widget.AppCompatImageView; import android.content.Context; import android.util.AttributeSet; public class CustomImageView extends AppCompatImageView{ public CustomImageView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean performClick() { super.performClick(); return true; } } |
このサンプルコードでは移動座標を TextView で表示していますが、LinearLayoutやRelativeLayoutのレイアウトを使うとTextViewを表示させないケースでは問題ないのですが、TextViewを表示や、何か別の作業をこのACTION_MOVEで行うとドラッグが重くなります。これはConstraintLayoutを使とうまくいきます。ConstraintLayoutのほうが処理が軽いからだと思われます。
また、カスタムImageViewなので
com.example.xxx.CustomImageView
のようにpackage name + class name で作成します。(そのままコピペしないように)
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text_view" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_marginTop="40dp" app:layout_constraintDimensionRatio="w,1:1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <!-- Your package name + class name --> <com.example.testimageviewdrag.CustomImageView android:id="@+id/image_view" android:src="@drawable/bag" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:contentDescription="@string/description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_view" /> </androidx.constraintlayout.widget.ConstraintLayout> |
リソース
strings.xml
1 2 3 4 |
<resources> <string name="app_name">Your App Name</string> <string name="description">bag</string> </resources> |
これで実行して、画像をドラッグしてみましょう
サンプル動画
logには画像をドラッグした座標が記録されています
logcat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
05:29:37.740 ...testimagedrag D/onTouch: ACTION_MOVE: dx=348, dy=120 05:29:37.780 ...testimagedrag D/onTouch: ACTION_MOVE: dx=380, dy=249 05:29:37.790 ...testimagedrag D/onTouch: ACTION_MOVE: dx=421, dy=346 05:29:37.830 ...testimagedrag D/onTouch: ACTION_MOVE: dx=458, dy=369 05:29:37.840 ...testimagedrag D/onTouch: ACTION_MOVE: dx=494, dy=542 05:29:37.880 ...testimagedrag D/onTouch: ACTION_MOVE: dx=544, dy=630 05:29:37.890 ...testimagedrag D/onTouch: ACTION_MOVE: dx=550, dy=712 05:29:37.910 ...testimagedrag D/onTouch: ACTION_MOVE: dx=586, dy=785 05:29:37.930 ...testimagedrag D/onTouch: ACTION_MOVE: dx=611, dy=829 05:29:37.940 ...testimagedrag D/onTouch: ACTION_MOVE: dx=626, dy=891 05:29:37.960 ...testimagedrag D/onTouch: ACTION_MOVE: dx=659, dy=949 05:29:37.980 ...testimagedrag D/onTouch: ACTION_MOVE: dx=663, dy=967 05:29:37.990 ...testimagedrag D/onTouch: ACTION_MOVE: dx=667, dy=976 05:29:38.010 ...testimagedrag D/onTouch: ACTION_MOVE: dx=667, dy=984 05:29:38.210 ...testimagedrag D/onTouch: ACTION_MOVE: dx=664, dy=982 |
関連:
- 画像の表示
- レイアウトファイルを使わずに画像表示
- 画像をドラッグする
- Matrixで画像を回転、フリップ、縮小させる
References:
layout(int, int, int, int) | Android Developers
View.OnTouchListener | Android Developers
MotionEvent | Android Developers