Android 基础之View中常见的API

本文详细介绍了Android中View的重要API,包括clip家族属性、ScrollView小技巧、XML布局符号使用、绘图技巧、控件状态及资源获取等。通过案例分析,如clipToPadding、android:fillViewport、android:overScrollMode、自定义滚动条样式等,帮助开发者更好地理解和应用这些API。同时,探讨了requestLayout()、Invalidate()、layout()的区别以及onDraw()的画笔操作。文章还涉及RecyclerView滑动状态、TextView高度获取、bringToFront()等控件状态和自定义View的常见API。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、简述

该篇文章记录本人学习和使用Android过程中的关于View的各种我所不熟悉但是很有用的api,都会包含一些小的案例。

2. xml布局中重要api

2.1 clip家族属性

2.1. clipToPadding

用途与用法: 常用于RecyclerView中(默认为true),设置是否要让该view的父布局去裁剪掉该view的paddinng值。

案例分析:如下图所示假若RecyclerView的xml中设置了android:paddingTop = "10dp",clipToPadding默认为ture,即裁剪掉padding值,那么和我们日常观察的一样,就是仍然有上边距进行滑动;clipToPadding为 false ,那么之前空白的padding会顶上去的,往下拉padding又会呈现了

2.1.2 android:clipChildren

用途:允许子View超出父View

使用注意事项:  1、只需在根节点设置android:clipChildren为false即可,默认为true,

                             注意:一定是在布局文件的根节点设置
                         2、可以通过android:layout_gravity控制超出的部分如何显示。
                         3、android:clipChildren的意思:是否限制子View在其范围内,我们将其值设置为false后那么当子控件的高度高于父控件时也会完全显示,而不会被压缩。

题外话:和前端里面的 overflow: hidden; 有点类似,默认就是android:clipChildren = "true",很显然前端里面要想显示超出父组件的区域可以使用auto,那么相应的 android里面可以将裁剪超出内容属性置为false.

效果:左边为弹出dialog的效果,右边为xml布局中的预览效果

     

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:clipChildren="false"
    android:layout_height="wrap_content">
    <!-- 注意  clipChildren 根布局 false-->
    <LinearLayout
        android:layout_alignParentBottom="true"
        android:background="#fff"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:clipChildren="false"
        android:layout_height="wrap_content">
        <!-- 注意 clipChildren  = false-->
        <!-- 注意 你裁剪的是哪个布局那么他的父亲布局得有该属性-->
        <LinearLayout
            android:id="@+id/rl_1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="128dp">
            <android.support.v7.widget.CardView
                android:id="@+id/cd"
                android:layout_gravity="bottom"
                android:layout_marginLeft="10dp"
                android:foreground="?android:attr/selectableItemBackground"
                android:layout_width="120dp"
                android:layout_height="150dp"
                card_view:cardBackgroundColor="#FFFFFF"
                card_view:cardCornerRadius="4dp"
                card_view:cardElevation="2dp"
                card_view:cardUseCompatPadding="true">
                <ImageView
                    android:src="@drawable/shop"
                    android:scaleType="fitXY"
                    android:layout_width="120dp"
                    android:layout_height="match_parent"/>
            </android.support.v7.widget.CardView>
            。。。。。。。
        </LinearLayout>
        <LinearLayout
            android:layout_marginTop="20dp"
            android:layout_width="match_parent"
            android:layout_height="45dp">
            。。。。。。。
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

2.2 scrollView中的小技巧

2.2.1 android:fillViewport="true"

开发中一般都设置Linerlayout为ScrollView的唯一子布局,如果没有设置android:fillViewport=”true”,即使给Linerlayout设置了 android:layout_heightt=”match_parent”也是没有作用的,它还是会按照wrap_coent来布局,最后无法铺满全屏。加上该属性就可以让唯一子布局的match_parent生效了。

下图的右侧的3,即为如下的ScrollView包裹的,是否使用 android:fillViewport = "true",会产生如下图的差异

    <ScrollView
        android:fillViewport="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </LinearLayout>
    </ScrollView>

2.2.2 android:overScrollMode="never"

 去除滑动尽头的显示效果,即用于滑动布局中取消滑动布局的光晕效果,从上往下拉时到边界有一个水滴状阴影的效果,可以运用该属性进行去除。

参考效果

2.2.3 修改scrollBar的样式

自定义滚动条首先我们要自定义drawable,scrollbars.xml自定义代码:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#0f0">
    </solid>
    <corners android:radius="2dp"></corners>

</shape>

xml布局中使用滑动属性 

    <ScrollView
        android:fillViewport="true"
        android:overScrollMode="never"
        android:scrollbars="vertical"
        android:scrollbarThumbVertical="@drawable/scrollbars"
        android:scrollbarSize="4dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
     。。。。。。。。。。。。。

    </ScrollView>

2.3. Android xml 符号使用

2.3.1Android xml 中的换行,空格等符号

空格 (&#x0020;)     Tab (&#x0009;)    回车 (&#x000D;)    换行 (&#x000A;)

添加<![CDATA[<<abc>>]]>包裹,通过编译.  对于书名号 << >> 

2.3.1string.xml中占位符的使用

占位符:%1$s
说明: %1:表示第一个占位符,依次类推,有%2,%3...
$s:表示该点位符的数据类型为string,若为$d则表示为数字

<string name="detail_date">时间:%1$s</string>
<string name="date">%1$s年%2$s月%3$d日</string>

// 使用
String date = context.getResources().getString(R.string.date,string1,string2,number3);

2.4 merge标签的使用

merge只能作为XML布局的根标签使用。当Inflate以merge开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

3.绘图相关的小技巧

3.1  canvas.clipRect(left,top,right,bottom)

    该Api用于View中的canvas绘制,该api用来表示裁剪画布的可见区域,也就是说后面画的内容只在这款区域进行显示。

作用说的很明显了,要想更加了解可以浏览这位博主的  android绘图canvas.clipRect()方法的作用

可视区域的显示还是显得尤为重要的。

3.2 canvas.save() 和 canvas.restore()

canvas.save( ):用来保存Canvas的状态 

canvas.restore( ):用来恢复Canvas旋转、缩放等之后的状态,当和canvas.save( )一起使用时,恢复到canvas.save( )保存时的状态。

        // 效果  竖着的线
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        // 由于逆时针旋转画布,所以看见的是竖直的
        canvas.rotate(90f,canvas.getWidth()/2,canvas.getHeight()/2);
        canvas.drawLine(50,300,400,300,paint);
        // 效果  横着的线
        canvas.save();
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        // 以画布的中心点旋转 
        canvas.rotate(90f,canvas.getWidth()/2,canvas.getHeight()/2);
        // 恢复画布的状态 即还原到save()的状态,即为画布为竖直
        canvas.restore();
        // 横向的一条线
        canvas.drawLine(50,300,400,300,paint);

案例与效果展示:

    /**
     * 对于平移而言 可以看作是移动的坐标系 --
     * 比如下面的效果是: 先绘制一条线段; 紧着者移动坐标轴 右下方移动,以移动后的坐标轴绘制线条 绘制完了之后还原
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawLine(0,0,100,0,mPaint);
        canvas.save();
        canvas.translate(20, mViewHeight-10);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.restore();
    }

 

3.3  LayoutParams 

    view.getLayoutParams 返回的类型是该View的父布局的类型.params  , 可以根据返回的params来获得该view的margin信息, 信息包本身就是子控件向父布局申请控件大小的,包括宽高包括各种margin等。

3.4 requestLayout() , Invalidate() , layout()之间的区别

requestLayout() :

控件会重新执行 onMesure() onLayout() ,比如 ScrollView中有LinearLaout ,LinearLayout里面有纵向排列的ImageView和TextView,那么假如ImageView的长宽发生了变化,而要立即在手机上显示这个变化的话,就可调用 imageView.requestLayout();这样的话ScrollView 会重新执行onMesure()这个方法会确定控件的大小然后在确定出自己的宽高,最后在执行onLayout(),这个方法是对所有的子控件进行定位的。

invalidate() :

是自定义View 的时候,重新执行onDraw()方法。默认用于主线程中刷新view,若耗费资源需要在子线程中刷新即需要使用handler,

postInvalidate() :

用于子线程中刷新view

 layout():

对控件进行重新定位执行onLayout()这个方法,比如要做一个可回弹的ScrollView,思路就是随着手势的滑动子控件滑动,那么我们可以将ScrollView的子控件调用layout(l,t,r,b)这个方法就行了。

3.5 onDraw()相关画笔操作

  1.paint.getTextBounds( )

  是将TextView 的文本放入一个矩形中, 测量TextView的高度和宽度

Rect mBounds = new Rect();
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(Color.YELLOW);
mPaint.setTextSize(30);
String text = String.valueOf(mCount);
mPaint.getTextBounds(text, 0, text.length(), mBounds);
float textWidth = mBounds.width();
float textHeight = mBounds.height();
canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2+ textHeight / 2, mPaint);

3.6  案例--View跟随手指移动

public class MoveView extends TextView {
    private float lastX, lastY;

    public MoveView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float x = event.getRawX();
        float y = event.getRawY();
        if (action == MotionEvent.ACTION_MOVE) {
            CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) getLayoutParams();
            //计算当前的左上角坐标
            float left = layoutParams.leftMargin + x - lastX;
            float top = layoutParams.topMargin + y - lastY;
            //设置坐标
            layoutParams.leftMargin = (int) left;
            layoutParams.topMargin = (int) top;
            setLayoutParams(layoutParams);
        }
        lastX = x;
        lastY = y;
        return true;
    }
}

4. Android 控件状态

4.1 RecyclerVIew滑动状态

 先看下RecyclerView源码对滑动状态的定义


    /**
     * 滑动停止,滑动都会调用。
     * The RecyclerView is not currently scrolling.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * 正在被拖拽,也就是在滑动,无论是都到顶部和底部都会调用该状态   
     * The RecyclerView is currently being dragged by outside input such as user touch input.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * 自动滑动,到达顶部或底部不会调用执行。
     * The RecyclerView is currently animating to a final position while not under
     * outside control.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_SETTLING = 2;
    private void initListener() {
        mRvScroll.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                Log.i("onScrollStateChanged","---"+newState+"---");
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                Log.e("onScrolled","--"+recyclerView.getScrollState()+"--"+" dx = "+dx+"   dy = "+dy );
            }
        });

        mRvScroll.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                Log.e("onInterceptTouchEvent","onInterceptTouchEvent执行");
                return false;
            }
            
           // 可以发现该方法并没有得到调用,只有 onInterceptTouchEvent返回true,该方法才会执行
            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                /*
                 *  ACTION_DOWN   = 0;
                 *  ACTION_UP     = 1;
                 *  ACTION_MOVE   = 2;
                 */
                Log.d("onTouchEvent","滑动状态:"+rv.getScrollState()+"  触摸状态"+e.getAction());
            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

            }
        });
    }

以下为RecyclerView上滑的过程;

可以发现 SCROLL_STATE_IDLE 状态时,即静止和停止的状态,起于此而终止于此。

07-18 18:37:51.605 5402-5402/com.example.mydairytestproject E/onScrolled: --0-- dx = 0   dy = 0
07-18 18:38:00.139 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.243 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.260 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.260 5402-5402/com.example.mydairytestproject I/onScrollStateChanged: ---1---
07-18 18:38:00.261 5402-5402/com.example.mydairytestproject E/onScrolled: --1-- dx = 0   dy = 13
07-18 18:38:00.277 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.278 5402-5402/com.example.mydairytestproject E/onScrolled: --1-- dx = 0   dy = 30
07-18 18:38:00.294 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.295 5402-5402/com.example.mydairytestproject E/onScrolled: --1-- dx = 0   dy = 21
07-18 18:38:00.310 5402-5402/com.example.mydairytestproject E/onInterceptTouchEvent: onInterceptTouchEvent执行
07-18 18:38:00.338 5402-5402/com.example.mydairytestproject I/onScrollStateChanged: ---2---
07-18 18:38:00.344 5402-5402/com.example.mydairytestproject E/onScrolled: --2-- dx = 0   dy = 7
07-18 18:38:00.361 5402-5402/com.example.mydairytestproject E/onScrolled: --2-- dx = 0   dy = 18
07-18 18:38:00.378 5402-5402/com.example.mydairytestproject E/onScrolled: --2-- dx = 0   dy = 18
07-18 18:38:00.661 5402-5402/com.example.mydairytestproject E/onScrolled: --2-- dx = 0   dy = 1
07-18 18:38:00.678 5402-5402/com.example.mydairytestproject E/onScrolled: --2-- dx = 0   dy = 1
07-18 18:38:00.711 5402-5402/com.example.mydairytestproject I/onScrollStateChanged: ---0---

4.2 TextView控件中获得控件实际高度

 getWidth(), getHeight():对应代码里的layout_width和layout_height。

getPaddiingLeft/Right/Top/Bottom():对应代码里的Padding。

getCompoundPaddingLeft/Top/Right/Bottom(): 翻译成中文就是获取混合的Padding, 既然是混合的,那么它的值也就是padding + 图片的大小 + drawablePadding的值。说得通俗点就是,它是获取文字区域到TextView边界之间的间隔

/**
 *  测量中无法通过 getHeight来获得,而且getHeight()获得的文本高度并不是实际的高度
 */
private static int getRealTextViewHeight(@NonNull TextView textView) {
    int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
    int padding = textView.getCompoundPaddingTop() +textView.getCompoundPaddingBottom();
    return textHeight + padding;
}

4.3  bringToFront( )

将这个view从父view中移除,然后再加入父view的顶端

  public void bringChildToFront(View child) {
        int index = indexOfChild(child);
        if (index >= 0) {
            removeFromArray(index);
            addInArray(child, mChildrenCount);
            child.mParent = this;
        }
    

测试运行效果:

    private void initView() {
        // FMLayout
        FrameLayout flRoot = findViewById(R.id.flRoot);
        View superView = new View(this);
        superView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 120));
        superView.setBackgroundColor(Color.parseColor("#ff0000"));
        flRoot.addView(superView, 0);

        View subView = new View(this);
        subView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 80));
        subView.setBackgroundColor(Color.parseColor("#00ff00"));
        /**
          *  无下面代码,效果左边
          *  有下面代码,效果右边
          *
          * superView.bringToFront();
        **/
        flRoot.addView(subView, 1);
    }

原先的顺序是 superView --> subView ; 移动顺序之后是  subView  --> superView

5.控件测量 , 资源获取,背景等

 

5.1 资源获取

5.1.1 自定义控件中获取style中资源

 TypeArray typeArray  =  getContext().obtainStyledAttributes(attrs,R.styable.xxx);

TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION);
mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, DEFAULT_ANIM_ALPHA_START);
mExpandableTextId = typedArray.getResourceId(R.styleable.ExpandableTextView_expandableTextId, R.id.expandable_text);

5.1.2 java代码中资源获取

1. 获得数组资源

    在values下新建Values resource file,起名为 arrays.xml。例如写下如下数据:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="city">
        <item>北京</item>
        <item>上海</item>
        <item>南京</item>
    </array>
</resources>

  在Java代码中如何获得呢,可以这样写:

String[] stringArray = getResources().getStringArray(R.array.city);
Log.e("数据", Arrays.asList(stringArray).toString());
//output: [北京, 上海, 南京]

5.2背景设置与状态

5.2.1 setAlive()

android:state_activated

    一个布尔值,如果设置为ture,那么这个项目应该在对象被持久选择时使用(如对象的高亮状态),否则应该在对象没有被激活时使用这个状态。

所以,如果遇到需要展示2个或者2个以上获取焦点的View的界面,不妨使用此方法。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 这里通过设置activated 状态 可以解决EditText获得焦点的问题-->
    <item android:state_activated="false">
        <shape>
            <corners android:radius="30dp"/>
            <solid android:color="@color/account_input_bg_color"/>
            <stroke android:width="1dp" android:color="@color/account_input_bg_color"/>
        </shape>
    </item>

    <item android:state_activated="true">
        <shape>
            <corners android:radius="30dp"/>
            <solid android:color="@color/account_input_bg_color"/>
            <stroke android:width="1dp" android:color="@color/login_input_active"/>
        </shape>
    </item>

</selector>

5.2.2 button背景

有时候我们点击button按钮会有一些其他设置的效果,可是效果不太好,这时候可以考虑直接使用TextView来替代他

6. 自定义view常用api

6.1 setWillNotDraw

ViewGroup 出于性能的考虑默认会设置不执行 onDraw()方法,那么我们如何让我们自定义的组建继承自ViewGroup还能自由的使用绘图呢,可以初始化的时候调用 setWillNotDraw(false)

7.  fileProvider使用

https://www.jianshu.com/p/f0b2cf0e0353

Android点击图标重新启动问题

8. 其他技巧记录

列表中使用Textview + checkbox 做内容选择,一般效果是我们点击文字对应的checkbox也能作出响应,这时候其实我们应该取消checkbox点击事件,然后将事件都绑定在父布局上

    <RelativeLayout
        android:id="@+id/rlCheck"
        android:background="#9f00"
        android:layout_marginTop="100dp"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_centerVertical="true"
            android:text="2020年快乐"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <!-- clickable = false -->
        <CheckBox
            android:clickable="false"
            android:focusableInTouchMode="false"
            android:layout_centerVertical="true"
            android:id="@+id/innerCheck"
            android:layout_alignParentRight="true"
            android:layout_marginRight="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </RelativeLayout>
 mCheckBox.setChecked(isChecked);
 // 父亲布局
 rlCheck.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isChecked = !isChecked;
                mCheckBox.setChecked(isChecked);
            }
  });

总结

暂时就是这么多内容,后面也会持续增加,并且进行细化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值