Posted at

カスタムビューでボタンを作る

More than 1 year has passed since last update.

例えば以下のようなボタンを作りたいとします。

custom_button_view.png

デフォルトのボタンでもアイコンは付けられるみたいですが、複雑なレイアウトを作るのはできないみたいです。

なので、カスタムビューを使って実現します。


レイアウト作成

まずはボタンにしたいカスタムビューのレイアウトファイルを作成します。

custom_button_view_layout.png

ルートレイアウトはなんでもいいのですが、今回はConstraintLayoutを使用しました。

次にこのレイアウトをボタンのような外見にします。

手順はルートレイアウトのstyle属性にボタンのスタイルを設定するだけです。

<!-- view_custom_buttom.xml -->

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button">
<!-- ↑ここ! -->

...

</android.support.constraint.ConstraintLayout>

これで、見た目はボタンそっくり、押したときのリップルもちゃんと表示されデフォルトと瓜二つになりました。

custom_button_view_style.png


カスタムビュークラス作成

次にカスタムビューのクラスを作成します。

// CustomButtonView.java

public class CustomButtonView extends ConstraintLayout {
public CustomButtonView(Context context) {
this(context, null);
}

public CustomButtonView(Context context, AttributeSet attrs) {
super(context, attrs);

init();
}

private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.view_custom_button, this);
}
}


完成?

これでいったんActivityに埋め込んでみましょう。

<!-- activity_main.xml -->

....

<***.***.CustomButtonView
android:id="@+id/custom_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

...

onClickリスナーもセットしておきます。

// MainActivity.java

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// インスタンスを取得
CustomButtonView customButton = findViewById(R.id.custom_button);

// onClickにリスナーを設定
customButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "Click!", Toast.LENGTH_LONG).show();
}
});
}
}

さて、これで上手くいきそう思えましたが、動かしてみるとカスタムボタンを押してもonClickリスナーが呼ばれません。

調べてみると以下の記事が参考になりました。

CustomViewのOnClickが反応しない時の対処方法 - Qiita

どうやらカスタムビューの場合はOnClickの処理は自分で書かないといけないみたいです。


CustomButtonViewにonClickの処理を追加

上記の記事を参考に以下を追加します。

// CustomButtonView.java

public class CustomButtonView extends ConstraintLayout {
...

@Nullable
private OnClickListener listener;

@Override
public void setOnClickListener(@Nullable OnClickListener l) {
this.listener = l;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (listener != null) {
post(new Runnable() {
@Override
public void run() {
listener.onClick(CustomButtonView.this);
}
});
}
}

return super.dispatchTouchEvent(ev);
}
}

custom_button_view_click.png

これでonClickが走るようになりました。


ボタンの範囲外にずらしたときの動作を修正

これで完成のように思えますがデフォルトのボタンとは微妙に異なる動作があります。


  • ボタンをタップ

  • ボタンを押したまま指をボタンの範囲外にずらす

  • 画面から離す

上の手順を行うとデフォルトのボタンではonClickは起こらないのですが、作成したCustomButtonViewではイベントが発生してしまいます。

これを修正するには以下のようにACTION_UPのときの座標がボタンの範囲内にあるかをチェックして、範囲内の場合にだけリスナーを呼ぶようにします。

// CustomButtonView.java

class CustomButtonView extends ConstraintLayout {
...

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
// 範囲内のチェックを追加
if (listener != null && checkInside(ev)) {
post(new Runnable() {
@Override
public void run() {
listener.onClick(CustomButtonView.this);
}
});
}
}

return super.dispatchTouchEvent(ev);
}

private boolean checkInside(MotionEvent ev) {
int[] point = new int[2];

getLocationOnScreen(point);

int x = point[0];
int y = point[1];

return (ev.getRawX() >= x && ev.getRawX() <= x + getWidth()) &&
(ev.getRawY() >= y && ev.getRawY() <= y + getHeight());
}
}


完成!

これで、見た目も動作もデフォルトのボタンとほぼ同様なカスタムビューボタンを作ることができました。