前回作ったコントロールでは、動的にadd もしくは remove した時に正常に動作しませんでした。
今回は ViewGroup を直接継承し実装してみました。
子要素の配置を行うためいに ViewGroup#onLayout をオーバーライドします。
ここで View#getWidth の戻り値は 0 となってしまうため、 View#getMeasuredWidthを使用します。
ですが、素の状態では View#getMeasuredWidth の戻り値も 0 となり、コントロールのサイズを取得できません。
そこでまず ViewGroup#onMeasure をオーバーライドし子要素に対してView#measureを呼び出します。
以下の呪文を記述
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = View.resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = View.resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
this.setMeasuredDimension(width, height);
int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
int childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(childWidthSpec, childHeightSpec);
}
}
これを書けば、View#getMeasuredWidthで値が取得できるようになります。
ちゃんと理解してはいない。
あとは前回 View#onWindowFocusChangedのなかに書いてた事をView#onLayoutに記述してやればOK。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = this.getChildCount();
// 行の高さ
int rowHeight = 0;
// 現在の行の幅
int currentRowWidth = 0;
int rowMaxWidth = this.getWidth();
int top = 0;
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
if (child.getVisibility() != View.GONE) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (rowMaxWidth < currentRowWidth + childWidth) {
// はみ出すので演算をやり直す。
// 次行の位置を設定
top += rowHeight < childHeight ? childHeight : rowHeight;
// 現在行の高さを初期化
rowHeight = 0;
// 現在行の幅を初期化
currentRowWidth = 0;
}
// コントロールを適切な位置に配置
child.layout(
currentRowWidth,
top,
currentRowWidth + childWidth,
top + childHeight);
// 行の高さを更新する必要があるか
rowHeight =
rowHeight < childHeight ? childHeight : rowHeight;
// 現在のコントロール幅を合計値に加算する
currentRowWidth += childWidth;
}
}
}
良い感じ良い感じ。
実際にはMarginなんかが設定されるハズ。
なの以下の クラス をInner Classとして作成。
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context context, AttributeSet attr) { super(context, attr); }
public LayoutParams(int width, int height) { super(width, height); }
public LayoutParams(ViewGroup.LayoutParams source) { super(source); }
public LayoutParams(MarginLayoutParams source) { super(source); }
}
MarginLayoutParams を継承しただけ。今後を考えてInner Classとした。
このクラスを定義しただけで、(WrapLayout.LayoutParams)child.getLayoutParams() なんてした日には楽しい結末(ClassCastException)が待っているので、
以下の Method を Override
・ ViewGroup#generateLayoutParams
・ ViewGroup#generateDefaultLayoutParams
・ ViewGroup#checkLayoutParams
・ ViewGroup#generateLayoutParams
@Override
public WrapLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new WrapLayout.LayoutParams(getContext(), attrs);
}
@Override
protected WrapLayout.LayoutParams generateDefaultLayoutParams() {
return new WrapLayout.LayoutParams(WrapLayout.LayoutParams.WRAP_CONTENT, WrapLayout.LayoutParams.WRAP_CONTENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof WrapLayout.LayoutParams;
}
@Override
protected WrapLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new WrapLayout.LayoutParams(p);
}
設定された margin を使って配置を行うように ViewGroup#onLayout に修正を加える。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = this.getChildCount();
// 行の高さ
int rowHeight = 0;
// 現在の行の幅
int currentRowWidth = 0;
int rowMaxWidth = this.getWidth();
int top = 0;
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
if (child.getVisibility() != View.GONE) {
WrapLayout.LayoutParams lp =
(WrapLayout.LayoutParams)child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTotaWidth = childWidth + lp.rightMargin + lp.leftMargin;
int childTotalHeight = childHeight + lp.topMargin + lp.bottomMargin;
if (rowMaxWidth < currentRowWidth + childTotaWidth) {
// はみ出すので演算をやり直す。
// 高さを設定
top += rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
// 現在行の高さを初期化
rowHeight = 0;
// 現在行の幅を初期化
currentRowWidth = 0;
}
child.layout(
currentRowWidth + lp.leftMargin,
top + lp.topMargin,
currentRowWidth + childWidth + lp.rightMargin,
top + childHeight + lp.bottomMargin);
rowHeight =
rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
currentRowWidth += childTotaWidth;
}
}
}
これでMarginが有効になったWrapLayoutの出来上がり。
ソースコード全文(import等は割愛)
public class WrapLayout extends ViewGroup {
//==================================================================
// constructor
//==================================================================
public WrapLayout(Context context) { super(context); }
public WrapLayout(Context context, AttributeSet attrs) { super(context, attrs); }
public WrapLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
//==================================================================
// public methods
//==================================================================
@Override
public WrapLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new WrapLayout.LayoutParams(getContext(), attrs);
}
//==================================================================
// protected methods
//==================================================================
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = View.resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = View.resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
this.setMeasuredDimension(width, height);
int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
int childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(childWidthSpec, childHeightSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = this.getChildCount();
// 行の高さ
int rowHeight = 0;
// 現在の行の幅
int currentRowWidth = 0;
int rowMaxWidth = this.getWidth();
int top = 0;
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
if (child.getVisibility() != View.GONE) {
WrapLayout.LayoutParams lp =
(WrapLayout.LayoutParams)child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTotaWidth = childWidth + lp.rightMargin + lp.leftMargin;
int childTotalHeight = childHeight + lp.topMargin + lp.bottomMargin;
if (rowMaxWidth < currentRowWidth + childTotaWidth) {
// はみ出すので演算をやり直す。
// 高さを設定
top += rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
// 現在行の高さを初期化
rowHeight = 0;
// 現在行の幅を初期化
currentRowWidth = 0;
}
child.layout(
currentRowWidth + lp.leftMargin,
top + lp.topMargin,
currentRowWidth + childWidth + lp.rightMargin,
top + childHeight + lp.bottomMargin);
rowHeight =
rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
currentRowWidth += childTotaWidth;
}
}
}
@Override
protected WrapLayout.LayoutParams generateDefaultLayoutParams() {
return new WrapLayout.LayoutParams(WrapLayout.LayoutParams.WRAP_CONTENT, WrapLayout.LayoutParams.WRAP_CONTENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof WrapLayout.LayoutParams;
}
@Override
protected WrapLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new WrapLayout.LayoutParams(p);
}
//==================================================================
// inner class
//==================================================================
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context context, AttributeSet attr) { super(context, attr); }
public LayoutParams(int width, int height) { super(width, height); }
public LayoutParams(ViewGroup.LayoutParams source) { super(source); }
public LayoutParams(MarginLayoutParams source) { super(source); }
}
}