そこにいろんな形の画像でボタンを作るには、背景を透過にした絵のイメージ画像を使う
この方法で実装した際、Viewの絵の部分をクリックしたときに動作させたいが、画像の余白(透過)部分を押しても反応してしまう問題がある
三角形の画像を想定したとき、
Viewの余白を押したときは反応せず、画像部分を押したときに反応する処理を考える
理論
ベクトルを使って押下判定を組む三角形の頂点座標を$O$、$A$、$B$、タッチしたときの座標$T$とする
-
$ \vec{OT} = \vec{t}, \vec{OA} = \vec{a},\vec{OB} = \vec{b} $ とおくと$\vec{t}$は以下のように表せられる
証明は省略するが こんな感じ
| 条件 | 座標$T$の位置 | ||
|---|---|---|---|
| $k_1=0$ | かつ | $k_2=0$ | 点$O$ |
| $k_1=1$ | かつ | $k_2=0$ | 点$A$ |
| $k_1=0$ | かつ | $k_2=1$ | 点$B$ |
| $0<k_1<1$ | かつ | $k_2=0$ | 線分$OA$上 |
| $k_1=0$ | かつ | $0<k_2<1$ | 線分$OB$上 |
| $0<k_1$ かつ $0<k_2$ かつ $k_1+k_2=1$ | 線分$AB$上 | ||
| $0<k_1$ かつ $0<k_2$ かつ $k_1+k_2<1$ | $\Delta OAB$ 内 | ||
| 上記以外 | $\Delta OAB$ 外 | ||
次に$k_1$、$k_2$の求め方
-
$
\vec{t} =
\begin{pmatrix}
t_x \\
t_y
\end{pmatrix}
,
\vec{a} =
\begin{pmatrix}
a_x \\
a_y
\end{pmatrix}
,
\vec{b} =
\begin{pmatrix}
b_x \\
b_y
\end{pmatrix}
$
とする
\[ k_1 = \frac{t_x b_y - t_y b_x}{a_x b_y - a_y b_x} \\ k_2 = \frac{a_x t_y - a_y t_x}{a_x b_y - a_y b_x} \]
Androidで実装
まずは専用のImageViewを作るAndroidはClick時の処理はView.OnClickListenerに実装する
このClickLisnerの実行条件はボタンが押された状態から、リリースされた時
なので、この三角形押下判定をボタン押下判定時に仕込むと、条件の絞り込みが楽になる
/**
* 三角形の中でのみ押下判定ができるImageViewクラス
*/
public class MyImageView extends ImageView {
// 三角形の座標情報
protected final PointF O; // 頂点O
protected final PointF A; // 頂点A
protected final PointF B; // 頂点B
{ // イニシャライザ。画像サイズを1×1として、座標位置を指定しておく
O = new PointF(0.5f, 0.0f);
A = new PointF(0f, 1f);
B = new PointF(1f, 1f);
}
// コントラクタ群
public MyImageView(Context context) { super(context);}
public MyImageView(Context context, AttributeSet attrs) {super(context, attrs);}
public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()== MotionEvent.ACTION_DOWN) {
float x = event.getX(), y = event.getY();
// 押下イベント通知のとき、条件判定を行い、三角形外であれば押下を処理しない
// これにより、リリース時に、むやみにClick処理が動作しない
if(!isTouch(new PointF(x, y))) {
return false;
}
}
return super.onTouchEvent(event);
}
/**
* 押下判定
* タッチ座標が三角形の内側か判定する
* @param T タッチ座標
* @return <li>true:内側<li>false:外側
*/
private boolean isTouch(final PointF T) {
final PointF OA, OB, OO, OT;
// ベクトル化
int w = getWidth(), h = getHeight();
OO = new PointF(w * O.x, h * O.y);
OA = new PointF(w * A.x - OO.x, h * A.y - OO.y);
OB = new PointF(w * B.x - OO.x, h * B.y - OO.y);
OT = new PointF(T.x - OO.x, T.y - OO.y);
// 連立方程式に持ち込んで、kを取得
float a = OA.x * OB.y - OB.x * OA.y;
if (a == 0) throw new IllegalStateException("座標計算ができません.");
float k1, k2;
k1 = (OT.x * OB.y - OB.x * OT.y) / a;
k2 = (OA.x * OT.y - OT.x * OA.y) / a;
return 0 <= k1 && 0 <= k2 && k1 + k2 <= 1;
}
}
あとは、普通に表示処理、押されたときの処理を組んでいくクリック処理はこんなもん
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// クリックでトースト表示
findViewById(R.id.my_button).setOnClickListener(v -> {Toast.makeText(v.getContext(), "テスト", Toast.LENGTH_SHORT).show();});
}
}
layout設定やら
# layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView android:text="Hello World!" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.example.kazuki.myapplication.MyImageView
android:id="@+id/my_button"
android:src="@drawable/mybtn_serect"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
一応セレクタで押下画像を設定して、押している状態がわかるようにしとく
# drawable/mybtn_serect.xml
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/image2" />
<item android:drawable="@drawable/image1" />
</selector>
できた!!

0 件のコメント:
コメントを投稿