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 中的换行,空格等符号
空格 ( ) Tab (	) 回车 (
) 换行 (
)
添加<![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
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);
}
});
总结
暂时就是这么多内容,后面也会持续增加,并且进行细化。