本記事では、Fragment内でタッチ操作が発生した際の座標を取得する方法とそれをActivityで扱う方法を紹介します。コードは一番下にあります。
画面上でのタッチ座標取得そのものをまだやったことない方はこちらを先に読むことをオススメします(非常にシンプルなコードとなっております)。
画面上タッチ座標の取得方法(onTouchEventの使い方)
Androidにて画面のタッチ座標取得をやったことない方は、まず以下記事を見てください。
非常にシンプルなサンプルコードを載せています。
上記事の内容を意識して、応用編の本記事をどうぞ。
作成したアプリの動作の様子(動画)
以下のような動作をするアプリを作成しました。
作成したアプリの仕様(Fragment+onTouchEvent)
メインの機能として以下を実装しています。
- 一定範囲(Fragment)内でのタッチ座標の取得
- Fragmentのサイズを取得し、タッチ座標を範囲に対する割合(0~100%)に変換して表示
- 上記一定範囲(Fragment)上のタッチ座標をMainActivityに返し表示
おまけの機能として以下を実装しています(動作確認と見やすさ向上の都合)。
- 画面全体のタッチ座標を取得・表示
- Fragment上タッチ操作時には四角形(Rect)を描画
Fragment上タッチ座標が0未満、もしくはFragmentのサイズ以上の場合は処理しないようにしてタッチ範囲を制限します。
機能の分担
- タッチ操作の検出・座標の取得:OnTouchEvent()
- Fragmentの描画サイズの取得:ViewTreeObserver.OnGlobalLayoutListenerのonGlobalLayout()
- Fragment上タッチ座標の範囲判定:Fragmentの描画サイズとの比較
- Fragmentで取得した座標をMainActivityに返す:インターフェースSampleFragmentListeneのonGetValue
- 図形の描画:onDraw()のdrawRect
画面に描画されるまでFragmentの描画サイズは取得できないので工夫が必要でした。
サンプルコード
以下がMainActivityのXMLです。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text="◆タッチされた座標"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=" X:"/> <TextView android:id="@+id/textview_x" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=""/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=" Y:"/> <TextView android:id="@+id/textview_y" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=""/> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text="◇タッチされた座標(Fragment内)"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=" X:"/> <TextView android:id="@+id/textview_fg_x" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=""/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=" Y:"/> <TextView android:id="@+id/textview_fg_y" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" android:text=""/> </LinearLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/fragment" android:name="com.example.shin.getcoordinates04.SampleFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="30dp"/> </FrameLayout> </LinearLayout>
以下がMainActivityのJavaファイルです。
package com.example.shin.getcoordinates04; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements SampleFragment.SampleFragmentListener { private TextView tv_x; private TextView tv_y; private TextView tv_fg_x; private TextView tv_fg_y; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_x = findViewById(R.id.textview_x); tv_y = findViewById(R.id.textview_y); tv_fg_x = findViewById(R.id.textview_fg_x); tv_fg_y = findViewById(R.id.textview_fg_y); } @Override public boolean onTouchEvent(MotionEvent event) { //XY座標の取得 float x_coor = event.getX(); float y_coor = event.getY(); //TextViewに表示 tv_x.setText(String.valueOf(x_coor)); tv_y.setText(String.valueOf(y_coor)); return true; } //インターフェース関連(Fragmentからの値取得) @Override public void onGetValue(float fg_x,float fg_y){ Log.d("Fragment info from onGetValue:",String.valueOf(fg_x)); tv_fg_x.setText(String.valueOf(fg_x)); tv_fg_y.setText(String.valueOf(fg_y)); } }
以下がFragmentのXMLです。
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#505050" android:id="@+id/fragment_toplayout"> <TextView android:text="@string/fragment_textview" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"/> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right"> <TextView android:id="@+id/fragment_textview_x" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" /> <TextView android:id="@+id/fragment_textview_y" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/MyTextStyle" /> </LinearLayout> </FrameLayout>
以下がFragmentのJavaファイルです。
package com.example.shin.getcoordinates04; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; public class SampleFragment extends Fragment { //表示用 private TextView fg_tv_x; private TextView fg_tv_y; //図形描画用 Paint paint; //Fragmentのサイズ取得関連 private FrameLayout topContainer; private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener; private float fragment_w = 0; private float fragment_h = 0; //MainActivityに呼ばれるインターフェース(値をMainActivityに返すため) public interface SampleFragmentListener{ void onGetValue(float fg_x,float fg_y); } //上記インターフェースのフィールド private SampleFragmentListener listener; @Override public void onAttach(@NonNull Context context){ super.onAttach(context); //contextをlisnerに代入 if(context instanceof SampleFragmentListener){ listener = (SampleFragmentListener) context; } } public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState){ paint = new Paint(); View view = inflater.inflate(R.layout.fragment_main,container, false); //Fragment上UIのid取得 fg_tv_x = view.findViewById(R.id.fragment_textview_x); fg_tv_y = view.findViewById(R.id.fragment_textview_y); topContainer = view.findViewById(R.id.fragment_toplayout); //図形描画用 topContainer.addView(new AnimView(this.getActivity())); //Fragmentのサイズ取得用 globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener(){ @Override public void onGlobalLayout(){ //Fragmentのサイズ取得 fragment_w = topContainer.getWidth(); fragment_h = topContainer.getHeight(); //removeOnGlobalLayoutListenerの削除 topContainer.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener); } }; topContainer.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener); return view; } //Fragment上図形描画用 class AnimView extends View{ Paint paint; //XY座標の取得 float cx; float cy; //XY座標(割合表示用) double cx_ratio; double cy_ratio; public AnimView(Context context){ super(context); paint = new Paint(); } @Override protected void onDraw(Canvas canvas){ paint.setStrokeWidth(10); paint.setStyle(Paint.Style.STROKE); canvas.drawRect(cx-20,cy-20,cx+20,cy+20, paint); } @Override public boolean onTouchEvent(MotionEvent event) { //XY座標取得 cx = event.getX(); cy = event.getY(); //XY座標を「Fragmentサイズに対しての割合」に変換+小数点切り捨て cx_ratio = Math.floor(cx/fragment_w*100); cy_ratio = Math.floor(cy/fragment_h*100); //タッチした座標がFragmentの範囲内なら処理 if(((cx>=0) && (cx <= fragment_w)) && ((cy>=0) && (cy <= fragment_h))){ fg_tv_x.setText("X :"+String.valueOf(cx_ratio)+"%"); fg_tv_y.setText("Y :"+String.valueOf(cy_ratio)+"%"); //MainActivityに値を返す if(listener != null){ listener.onGetValue(cx,cy); } //タッチ動作の種類に応じて分岐(描画する図の色を分岐) switch(event.getAction()){ case MotionEvent.ACTION_MOVE: paint.setColor(Color.argb(255,255,255,0)); break; case MotionEvent.ACTION_UP: paint.setColor(Color.argb(255,255,0,0)); break; } //再描画 invalidate(); } return true; } } }
もっといい方法はあるかもしれませんが、作りたいものが作れたのでとりあえず満足しています。
コメント