長いですが簡単です。

 少しいろいろやってくると、複数のUI部品(ウィジェット)をまとめて、その中で完結した処理をしたいと言うことが出てくると思う。普通にアクティビティに記述してもいいが、アクティビティのコードが見にくくなったり、処理を再利用するときに、いちいちコードを記述しなければならないので面倒になる場合がある。そんなときにlayout.xmlにUI部品をレイアウトし、さらに処理を行うコーディングを一つのUI部品のように使えると便利だ。
 そこでlayout.xmlにUI部品を設定して、それを読み込み、一つのUI部品のように利用するカスタムビューを作る方法。

 これを行うには3つの処理が必要になる
  1. UI部品を記述したレイアウトのxmlファイルを作る
  2. レイアウトファイルを読み込み、処理を記述するViewGroupを継承するクラスを作る
  3. 作成したViewGroupをアクティビティのレイアウトのxmlファイルに記述する
 まずはレイアウトを作成する。今回はButtonを一つ設置しただけのシンプルなもの。
 このmergeタグは指定したViewGroupにUI部品の要素を追加するためのタグ。これを使うとアクティビティに設置したViewGroupを継承するレイアウト系の部品(LinearLayoutとかConstraintLayout)に追加することができる。なぜこうするかというと、アクティビティにViewGroupを設置し、さらにこのレイアウトファイルにもViewGroupを設置するとViewGroupをネストすることになってしまうから。
 しかしこのままだとデザイナが使えなくなってしまうため、tools:parentTagで結合する親のViewGroupの種類をしておけばAndroidStudioのデザイナを利用することができる。
 つまり、今回はConstraintLayoutにこのxmlファイルを結合することになる。カスタムビューの本体はConstraintLayoutであり、それがアクティビティのxmlファイルに設置するConstraintLayoutになる。
<?xml version="1.0" encoding="utf-8"?>
<merge 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="wrap_content"
    tools:parentTag="android.support.constraint.ConstraintLayout">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
</merge>
 次にConstraintLayoutを継承したクラスを作成する。これがアクティビティのレイアウトファイルに設置されるGroupViewになり、さきほどの<merge>に記述したConstraintLayoutになる。
 コンストラクタはxmlから生成される場合と動的に生成する場合では使うコンストラクタが異なるので複数の定義が必要。この定義については調べるか、このようにすれば自動的に生成してくれる
 initでは、さきほど作ったxmlファイルを読み込み設定し、そのボタンのインスタンスを取得し、イベントを設定している。またボタンが押されたときのイベントをアクティビティ側にも伝えるため、アクティビティ側に設定されたメソッドを実行するコールバックを実行する。さらにこのカスタムビューで行う処理もここに記述する。
 mSetOnButtonClickListenerにはアクティビティで設定した関数の参照が入り、アクティビティ側にボタンが押されたときのコールバックを行うことができる。
class MyCustomView : ConstraintLayout {
    //アクティビティが破棄されたら必ず実行される
    init {
        Log.v("nullpo", "initMyCustomView")

//ここで先ほど作成したxmlファイルを設定する View.inflate(context, R.layout.test_customview_layout, this)
//xmlに設定したボタンのインスタンスを取得してイベントを設定する
//ボタンが押されると実行される findViewById<Button>(R.id.button).setOnClickListener {
//アクティビティへのコールバック ボタンが押されたことをアクティビティに伝える mSetOnButtonClickListener?.invoke(it)
//ここでボタンが押されたときの様々な処理を行う
//もちろん別メソッドで処理してもいい Log.v("nullpo", "onButtonClick") } } //ボタンクリック時にアクティビティのメソッドを実行するイベントリスナーの定義 private var mSetOnButtonClickListener: ((View) -> Unit)? = null fun setOnButtonClickListener(func: (View) -> Unit): Unit { mSetOnButtonClickListener = func } //コンストラクタ
//動的に作成するときと、xmlファイルから生成されるときとそれぞれのコンストラクタが必要 constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} }
 アクティビティ側のレイアウトファイル。タグの名前をさきほど作成したConstraintLayoutを継承したクラスとして設定してあげる。
 ここではViewGroupがネストしてしまっているが、ViewGroupで複数の部品をひとまとめにして一つのUI部品のように扱っているため、ここでのネストは仕方がない。

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.to.gameapplication.MyCustomView
        android:id="@+id/constraintLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
 カスタムビューのUI部品のイベントが発生したときの処理を記述する。UI部品のイベントが発生したことをアクティビティ側に伝えることができる。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        constraintLayout.setOnButtonClickListener { 
            //ここで処理
        }
    }
}