35

Android でより複雑なカスタム ビューを作成する必要があります。最終的なレイアウトは次のようになります。

<RelativeLayout>
  <SomeView />
  <SomeOtherView />
  <!-- maybe more layout stuff here later -->
  <LinearLayout>
    <!-- the children -->
  </LinearLayout>
</RelativeLayout>

ただし、XML ファイルでは、これを定義したいだけです (SomeView、SomeOtherView などを定義せずに):

<MyCustomView>
  <!-- the children -->
</MyCustomView>

これは Android で可能ですか。可能であれば、それを行う最もクリーンな方法は何でしょうか? 私の頭に浮かんだ可能な解決策は、「addView()メソッドをオーバーライドする」と「すべてのビューを削除して後で再度追加する」ことでしたが、どちらに進むべきかわかりません...

ご協力いただきありがとうございます。:)

4

1 に答える 1

66

カスタム コンテナー ビューを作成することは絶対に可能であり、推奨されています。これは、Android が複合コントロールと呼ぶものです。そう:

public class MyCustomView extends RelativeLayout {
    private LinearLayout mContentView;

    public MyCustomView(Context context) {
        this(context, null);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //Inflate and attach your child XML
        LayoutInflater.from(context).inflate(R.layout.custom_layout, this);
        //Get a reference to the layout where you want children to be placed
        mContentView = (LinearLayout) findViewById(R.id.content);

        //Do any more custom init you would like to access children and do setup
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if(mContentView == null){
            super.addView(child, index, params);
        } else {
            //Forward these calls to the content view
            mContentView.addView(child, index, params);
        }
    }
}

のバージョンは必要に応じていくつでもオーバーライドできますaddView()が、最終的にはすべて、サンプルに配置したバージョンにコールバックします。このメソッドだけをオーバーライドすると、フレームワークは XML タグ内で見つかったすべての子を特定の子コンテナーに渡します。

次に、XML を次のように変更します。

res/layout/custom_layout.xml

<merge>
  <SomeView />
  <SomeOtherView />
  <!-- maybe more layout stuff here later -->
  <LinearLayout
      android:id="@+id/content" />
</merge>

使用する理由<merge>は、階層を単純化するためです。すべての子ビューはカスタム クラスにアタッチされます。これはRelativeLayout. を使用しない<merge>と、RelativeLayoutが別のRelativeLayoutにアタッチされ、すべての子にアタッチされてしまい、問題が発生する可能性があります。


コトリンのバージョン:


    private fun expand(view: View) {
        val parentWidth = (view.parent as View).width
        val matchParentMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY)
        val wrapContentMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

        view.measure(matchParentMeasureSpec, wrapContentMeasureSpec)
        val targetHeight = view.measuredHeight
        view.isVisible = true
        val animation: Animation = getExpandAnimation(view, targetHeight)

        view.startAnimation(animation)
    }

    private fun getExpandAnimation(
        view: View,
        targetHeight: Int
    ): Animation = object : Animation() {
        override fun applyTransformation(
            interpolatedTime: Float,
            transformation: Transformation
        ) {
            view.layoutParams.height =
                if (interpolatedTime == 1f) {
                    LayoutParams.WRAP_CONTENT
                } else {
                    (targetHeight * interpolatedTime).toInt()
                }

            view.requestLayout()
        }

        override fun willChangeBounds(): Boolean {
            return true
        }
    }.apply {
        duration = getDuration(targetHeight, view)
    }

    private fun collapse(view: View) {
        val initialHeight = view.measuredHeight
        val animation: Animation = getCollapseAnimation(view, initialHeight)

        view.startAnimation(animation)
    }

    private fun getCollapseAnimation(
        view: View,
        initialHeight: Int
    ): Animation = object : Animation() {
        override fun applyTransformation(
            interpolatedTime: Float,
            transformation: Transformation
        ) {
            if (interpolatedTime == 1f) {
                view.isVisible = false
            } else {
                view.layoutParams.height =
                    initialHeight - (initialHeight * interpolatedTime).toInt()
                view.requestLayout()
            }
        }

        override fun willChangeBounds(): Boolean = true

    }.apply {
        duration = getDuration(initialHeight, view)
    }

    /**
     * Speed = 1dp/ms
     */
    private fun getDuration(initialHeight: Int, view: View) =
        (initialHeight / view.context.resources.displayMetrics.density).toLong()
于 2012-06-25T22:22:30.033 に答える