android android:layout_margin,Android-自定义ViewGroup-增加layout_margin支持

不过由于我们没有考虑margin,所以暂时还不能支持margin属性的设置,即使你设置了,但是如果onLayout没有做处理也是不行滴...自定义的呀!

首先我们拿第一个控件设置一些个margin,然后内部去处理下:

custom_viewgroup_me.xml

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#ffff00ee">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginBottom="10dp"

android:layout_marginLeft="10dp"

android:layout_marginRight="10dp"

android:layout_marginTop="10dp"

android:background="#ff87addd"

android:text="aaaaaaaa哇咔咔哇咔咔"

android:textColor="#ff000000"

android:textSize="22sp" />

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#ffa43dee"

android:text="sssssss"

android:textColor="#ff000000"

android:textSize="12sp" />

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#ffa43dee"

android:text="wwwwwww"

android:textColor="#ff000000"

android:textSize="18sp" />

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#ffa43dee"

android:text="大幅度发"

android:textColor="#ff000000"

android:textSize="12sp" />

Then,我们要拿到margin属性,然后做几件事情:

1. 在onMeasure中增加容器的左右宽度,也就是之前计算的maxWidth = maxWidth + leftMargin + rightMargin; ---因为你设置了控件的左右间距,相当于增大了控件的宽度

2. 在onMeasure中增加容器的上下高度(就是高度),同理maxWidth = maxWidth + topMargin + bottomMargin;

3. 在onLayout中,绘制的left, top自然也要跟着进行位移,凸显出间距来 leftPos + leftMargin leftTop + rightMargin

4. 在onLayout中,之前累积的下一个控件的top值相应的要多增加top/bottom的间距leftTop += childH + topMargin + bottomMargin;

5. onLayout中的绘制范围右下角还是和之前一样(除非你有的控件可能受到对应影响,那样可能就是相对复杂的布局了)

A. 现在需要获取整个margin属性??

我们下一篇专门针对这个做一些学习...

下面我们就直接参考官方之前的做法自定义个LayoutParams继承MarginLayoutParams,然后在generateLayoutParams方法中返回(之前我有提到过这个方法):

@Override

public CustomViewGroupLastNew.LayoutParams generateLayoutParams(AttributeSet attrs) {

return new CustomViewGroupLastNew.LayoutParams(getContext(), attrs);

}

/**

* 这个是布局相关的属性,最终继承的是ViewGroup.LayoutParams,所以上面我们可以直接进行转换

* --目的是获取自定义属性以及一些使用常量的自定义

*/

public static class LayoutParams extends MarginLayoutParams {

public LayoutParams(Context c, AttributeSet attrs) {

super(c, attrs);

// Pull the layout param values from the layout XML during

// inflation. This is not needed if you don't care about

// changing the layout behavior in XML.

TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomLayoutLP);

///< TODO 一些属性的自定义

a.recycle();

}

public LayoutParams(int width, int height) {

super(width, height);

}

public LayoutParams(ViewGroup.LayoutParams source) {

super(source);

}

}

简单说明:至于怎么就获取的margin,然后怎么返给子控件的,大概可以跟到MarginLayoutParams里面去看.. next page,let't do it...

B. 获取到属性了我们就可以直接累代码了,相信积累了前面几篇文章的过渡,这些思考下应该问题不大了吧...直接看代码:

package me.heyclock.hl.customcopy;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

import android.util.AttributeSet;

import android.util.Log;

import android.view.Gravity;

import android.view.View;

import android.view.ViewGroup;

/*

*@Description: 自定义ViewGroup + 纵向垂直布局 + 单列

*@Author: hl

*@Time: 2018/10/25 10:18

*/

public class CustomViewGroupLastNew extends ViewGroup {

private Context context;///< 上下文

/**

* 计算子控件的布局位置.

*/

private final Rect mTmpContainerRect = new Rect();

private final Rect mTmpChildRect = new Rect();

public CustomViewGroupLastNew(Context context) {

this(context, null);

}

public CustomViewGroupLastNew(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public CustomViewGroupLastNew(Context context, AttributeSet attrs, int defStyleAttr) {

this(context, attrs, 0, 0);

}

public CustomViewGroupLastNew(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

this.context = context;

}

/**

* 测量容器的宽高 = 所有子控件的尺寸 + 容器本身的尺寸 -->综合考虑

*

* @param widthMeasureSpec

* @param heightMeasureSpec

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

///< 定义最大宽度和高度

int maxWidth = 0;

int maxHeight = 0;

///< 获取子控件的个数

int count = getChildCount();

for (int i = 0; i < count; ++i) {

View view = getChildAt(i);

///< 子控件如果是GONE - 不可见也不占据任何位置则不进行测量

if (view.getVisibility() != GONE) {

///< 获取子控件的属性 - margin、padding

CustomViewGroupLastNew.LayoutParams layoutParams = (CustomViewGroupLastNew.LayoutParams) view.getLayoutParams();

///< 调用子控件测量的方法getChildMeasureSpec(先不考虑margin、padding)

///< - 内部处理还是比我们自己的麻烦的,后面我们可能要研究和参考

final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, layoutParams.leftMargin + layoutParams.rightMargin, layoutParams.width);

final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, layoutParams.topMargin + layoutParams.bottomMargin, layoutParams.height);

///< 然后真正测量下子控件 - 到这一步我们就对子控件进行了宽高的设置了咯

view.measure(childWidthMeasureSpec, childHeightMeasureSpec);

///< 然后再次获取测量后的子控件的属性

layoutParams = (CustomViewGroupLastNew.LayoutParams) view.getLayoutParams();

///< 然后获取宽度的最大值、高度的累加

maxWidth = Math.max(maxWidth, view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin);

maxHeight += view.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

}

}

///< 然后再与容器本身的最小宽高对比,取其最大值 - 有一种情况就是带背景图片的容器,要考虑图片尺寸

maxWidth = Math.max(maxWidth, getMinimumWidth());

maxHeight = Math.max(maxHeight, getMinimumHeight());

///< 然后根据容器的模式进行对应的宽高设置 - 参考我们之前的自定义View的测试方式

int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);

int wSize = MeasureSpec.getSize(widthMeasureSpec);

int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);

int hSize = MeasureSpec.getSize(heightMeasureSpec);

///< wrap_content的模式

if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {

setMeasuredDimension(maxWidth, maxHeight);

}

///< 精确尺寸的模式

else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {

setMeasuredDimension(wSize, hSize);

}

///< 宽度尺寸不确定,高度确定

else if (wSpecMode == MeasureSpec.UNSPECIFIED) {

setMeasuredDimension(maxWidth, hSize);

}

///< 宽度确定,高度不确定

else if (hSpecMode == MeasureSpec.UNSPECIFIED) {

setMeasuredDimension(wSize, maxHeight);

}

}

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

///< 获取范围初始左上角 - 这个决定子控件绘制的位置,我们绘制理论可以从0,0开始,margin容器本身已经考虑过了...所以别和margin混淆了

int leftPos = getPaddingLeft();

int leftTop = getPaddingTop();

///< 获取范围初始右下角 - 如果考虑控件的位置,比如靠右,靠下等可能就要利用右下角范围来进行范围计算了...

///< 后面我们逐步完善控件的时候用会用到这里...

//int rightPos = right - left - getPaddingRight();

//int rightBottom = bottom - top - getPaddingBottom();

///< 由于我们是垂直布局,并且一律左上角开始绘制的情况下,我们只需要计算出leftPos, leftTop就可以了

int count = getChildCount();

for (int i = 0; i < count; ++i){

View childView = getChildAt(i);

///< 控件占位的情况下进行计算

if (childView.getVisibility() != GONE){

///< 获取子控件的属性 - margin、padding

CustomViewGroupLastNew.LayoutParams layoutParams = (CustomViewGroupLastNew.LayoutParams) childView.getLayoutParams();

int childW = childView.getMeasuredWidth();

int childH = childView.getMeasuredHeight();

///< 先不管控件的margin哈!

int cleft = leftPos + layoutParams.leftMargin;

int cright = cleft + childW;

int ctop = leftTop + layoutParams.topMargin;

int cbottom = ctop + childH;

///< 下一个控件的左上角需要向y轴移动上一个控件的高度 - 不然都重叠了!

leftTop += childH + layoutParams.topMargin + layoutParams.bottomMargin;

///< 需要一个范围,然后进行摆放

childView.layout(cleft, ctop, cright, cbottom);

}

}

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

int count = getChildCount();

for (int i = 0; i < count; ++i) {

///< 获取子控件的宽高

View view = getChildAt(i);

CustomViewGroupLastNew.LayoutParams layoutParams = (CustomViewGroupLastNew.LayoutParams) view.getLayoutParams();

Log.e("test", "layoutParams.height=" + layoutParams.height);

Log.e("test", "layoutParams.width=" + layoutParams.width);

Log.e("test", "view.getHeight()=" + view.getHeight());

Log.e("test", "view.getWidth()=" + view.getWidth());

Log.e("test", "view.getMeasuredHeight()=" + view.getMeasuredHeight());

Log.e("test", "view.getMeasuredWidth()=" + view.getMeasuredWidth());

}

}

@Override

public CustomViewGroupLastNew.LayoutParams generateLayoutParams(AttributeSet attrs) {

return new CustomViewGroupLastNew.LayoutParams(getContext(), attrs);

}

/**

* 这个是布局相关的属性,最终继承的是ViewGroup.LayoutParams,所以上面我们可以直接进行转换

* --目的是获取自定义属性以及一些使用常量的自定义

*/

public static class LayoutParams extends MarginLayoutParams {

public LayoutParams(Context c, AttributeSet attrs) {

super(c, attrs);

// Pull the layout param values from the layout XML during

// inflation. This is not needed if you don't care about

// changing the layout behavior in XML.

TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomLayoutLP);

///< TODO 一些属性的自定义

a.recycle();

}

public LayoutParams(int width, int height) {

super(width, height);

}

public LayoutParams(ViewGroup.LayoutParams source) {

super(source);

}

}

}

当然这个我们是比官方的简化了些,以为你有些属性我们还没有定义,也就是用来完善布局的属性,比如gravity、posion(就是靠左靠右啥的....当然随着控件越来越复杂肯定也需要做更多的处理...) - 里面增加的地方,前面的几点说明的注意看下:

0eb3ff1f74a6

image

0eb3ff1f74a6

image

效果:

0eb3ff1f74a6

image

心灵鸡汤 - 自定义控件是一条蛮长的路滴

黄磊

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您想要实现点击 RecyclerView 后跳转到一个新页面展示 RecyclerView 内的信息,这可以通过以下步骤实现: 1. 为 RecyclerView 中的每个 item 添加一个 onClickListener,监听点击事件。 2. 在 onClickListener 中获取被点击 item 的数据。 3. 将数据传递给新页面,用于展示。 4. 启动新页面,展示数据。 具体实现步骤如下: 1. 在 RecyclerView 的 Adapter 中为 item 添加 onClickListener: ``` public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private List<MyData> mDataList; private Context mContext; public MyAdapter(List<MyData> dataList, Context context) { mDataList = dataList; mContext = context; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { MyData data = mDataList.get(position); holder.bindData(data); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // item 被点击了 Intent intent = new Intent(mContext, DetailActivity.class); intent.putExtra("data", data); mContext.startActivity(intent); } }); } @Override public int getItemCount() { return mDataList.size(); } static class MyViewHolder extends RecyclerView.ViewHolder { private TextView mTextView; public MyViewHolder(View itemView) { super(itemView); mTextView = itemView.findViewById(R.id.text_view); } public void bindData(MyData data) { mTextView.setText(data.getText()); } } } ``` 2. 在新页面中获取传递的数据,并展示: ``` public class DetailActivity extends AppCompatActivity { private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); mTextView = findViewById(R.id.text_view); MyData data = getIntent().getParcelableExtra("data"); mTextView.setText(data.getText()); } } ``` 需要注意的是,MyData 类需要实现 Parcelable 接口,以便可以将数据传递给新页面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值