1、Android新特性
1、RecyclerView
-
分割线,分析官方提供的DividerItemDecoration:
public class DividerItemDecoration extends RecyclerView.ItemDecoration { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; private Drawable mDivider; private int mOrientation; private final Rect mBounds = new Rect(); //用于存储ItemView的范围 public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); //自定义分割线时照猫画虎 a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL && orientation != VERTICAL) { throw new IllegalArgumentException( "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); } mOrientation = orientation; } public void setDrawable(@NonNull Drawable drawable) { if (drawable == null) { throw new IllegalArgumentException("Drawable cannot be null."); } mDivider = drawable; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null) { return; } if (mOrientation == VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } @SuppressLint("NewApi") private void drawVertical(Canvas canvas, RecyclerView parent) { canvas.save(); final int left; final int right; if (parent.getClipToPadding()) { //默认true,表示不予许子View在Padding上绘制 left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0; right = parent.getWidth(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); //将ItemView范围存到mBounds final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } @SuppressLint("NewApi") private void drawHorizontal(Canvas canvas, RecyclerView parent) { canvas.save(); final int top; final int bottom; if (parent.getClipToPadding()) { top = parent.getPaddingTop(); bottom = parent.getHeight() - parent.getPaddingBottom(); canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom); } else { top = 0; bottom = parent.getHeight(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); final int right = mBounds.right + Math.round(child.getTranslationX()); final int left = right - mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } //设置ItemView的位置,mDivider.getIntrinsicHeight为Drawable的内部高度 @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
-
设置item增加和删除的默认动画:
setItemAnimator(new DefaultItemAnimator());
2、Palette
-
用来提取图片的色调:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(Palette palette) { Palette.Swatch swatch = palette.getDarkVibrantSwatch(); getSupportActionBar().setBackgroundDrawable(new ColorDrawable(swatch.getRgb())); } });
3、Dangerous Permission
-
Dangerous Permission是以组的形式给出,同一组的任何一个权限被授权了,其他权限也自动被授权。
<!-- CALENDAR 日历组 --> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> <!-- CAMERA 相机拍照组 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- CONTACTS 联系人组 --> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <!-- LOCATION 定位组 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- MICROPHONE 麦克风组 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- PHONE 组 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.USE_SIP" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <!-- SENSORS 传感器组 --> <uses-permission android:name="android.permission.BODY_SENSORS" /> <!-- SMS 组 --> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" /> <uses-permission android:name="android.permission.RECEIVE_MMS" /> <!-- STORAGE 存储组 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
基本用法
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1); }else{ callPhone(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == 1){ if (grantResults[0] == PackageManager.PERMISSION_GRANTED){ callPhone(); }else{ if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)){ Toast.makeText(this,"权限被拒绝",Toast.LENGTH_SHORT).show(); } } return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
4、7.0多窗口模式
- 当进入多窗口模式时,Activity会经历一个重新创建过程,最终停留在onPause状态,当我们点击Activity的窗口时,才会进入onResume。
- 默认开启多窗口模式的,禁用可以在AndroidManifest.xml中加人属性resizeableActivity="false"
5、SnackBar
//参数一一般最外层的View
Snackbar.make(mView,"标题",Snackbar.LENGTH_LONG).setAction("点击事件", new View.OnClickListener() {
@Override
public void onClick(View view) {
}
}).show();
6、TextInputLayout
- 包裹EditView,hint会在输入时显示在输入框上面,还可以显示错误信息,调用setErrorEnabled(true)和setError("请输入正确密码"),会显示在输入框下面。
7、NavigationView
-
放在DrawerLayout里, android:layout_gravity="start"表示是侧拉,headerLayout头部布局,menu对应的item:
<android.support.design.widget.NavigationView android:layout_width="wrap_content" android:layout_gravity="start" app:headerLayout="@layout/navigation_header" app:menu="@menu/drawer_view" android:layout_height="match_parent"> </android.support.design.widget.NavigationView>
-
app:menu="@menu/drawer_view":
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <!--single表示只能一个item被选中--> <group android:checkableBehavior="single"> <item android:id="@+id/nav_home" android:icon="@mipmap/ic_launcher_round" android:title="首页"/> <item android:id="@+id/nav_message" android:icon="@mipmap/ic_launcher_round" android:title="事项"/> <!--分组,会自动添加分割线--> <item android:title="其他"> <menu> <item android:id="@+id/nav_setting" android:icon="@mipmap/ic_launcher_round" android:title="设置"/> </menu> </item> </group> </menu>
-
监听点击:setNavigationItemSelectedListener
8、CollapsingToolbarLayout
- 属性:
app:contentScrim:收缩后最顶部的颜色
app:expandedTitleGravity="left|bottom",完全展开后title处的位置,默认left|bottom
app:collapsedTitleGravity = "left",收缩后title的位置,默认left
9、CoordinatorLayout
-
FloatingActionButton与CoordinatorLayout结合使用会有一个特殊效果,弹出SnackBar时,为了给SnackBar留出空间,FloatingActionButton会向上移动。
-
自定义Behavior
第一种,监听CoordinatorLayout的滑动状态,需要关注onStartNestedScroll和onNestedPreScroll:public class MyBehavior extends CoordinatorLayout.Behavior<View> { private int directionChange; //表示这次滑动要不要关心 @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { Log.d("----",nestedScrollAxes+""); return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } //滑动处理 @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { if (dy>0 && directionChange<0 || dy<0&&directionChange>0){ child.animate().cancel(); directionChange = 0; } directionChange += dy; if (directionChange>child.getHeight() && child.getVisibility() == View.VISIBLE){ //隐藏 }else if(directionChange<0 && child.getVisibility() == View.GONE){ //显示 } } }
第二种,监听另一个View,需要关注layoutDependsOn和onDependentViewChanged:
public class MyBehavior extends CoordinatorLayout.Behavior<View> { //返回需要关心的View @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof AppBarLayout; } //关心的View带来的变化 @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { child.setTranslationY(Math.abs(dependency.getY())); return true; } }
2、View体系
1、改变布局参数
-
ViewGroup.MarginLayoutParams
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); layoutParams.leftMargin = getLeft()+offsetx; layoutParams.topMargin = getTop() + offsetY; setLayoutParams(layoutParams);
2、动画
- Android提供了AnimatorListenerAdapter可选择的提供事件监听。
3、View体系
-
getSuggestedMinimumWidth:如果View没有设置背景,则返回mMinWidth,如果设置背景,就返回mMinWidth与Drawable的最小宽度的最大值。
-
getChildMeasureSpec:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
3、多线程编程
1、线程基础
- 调用Thread的start方法,开始进入运行状态,当线程执行wait方法后,线程进入等待状态,进入等待状态的线程需要其他线程通知才能返回运行状态。超时等待相当于在等待状态加上时间的限制,如果超过时间限制,则线程返回运行状态。当线程调用同步方法,如果线程没有获得锁则进入阻塞状态,当阻塞状态的线程获取锁时则重新回到运行状态。当线程执行完毕或者遇到意外异常终止时,都会进入终止状态。
- Thread本质上也是实现了Runnable。
- Callable与Runnable的区别:
Callable可以在任务接受后提供一个返回值,Runnable无法提供这个功能。
Callable中的call()方法可以抛出异常,而Runnable的run方法不能。
运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。调用Future的get方法获取结果时,当前线程会阻塞,直到call返回结果。
2、volatile
-
含义:一是线程修改了变量的值时,变量的新值对其他线程是立即可见的;二是进展使用指令重新排序。
-
看一段有可能重排序出现BUG的代码:
这段代码不一定会将线程中断,虽然这情况很小,但一旦发生就会造成死循环,线程1在运行时会将stop变量的值复制一份放在私有的工作内存中,当线程2更改了stop的值后,线程2突然需要去做其他的操作,这时就无法将更改的stop变量写入主存当中,当线程1就不会直到线程2对stop变量进行了更改。//线程1 boolean stop = false; while(!stop){ } //线程2 stop = true;
3、BlockingQueue
- offer:加入到队列中,如果队列可以容纳,返回true。
- put:加入到队列中,如果没有空间,则调用线程会被阻塞,直到有空间再继续。
- poll:获得队列排在首位的对象,取不到返回null。
- take:获得队列排在首位的对象,取不到一直等,阻塞当前线程,直到有新数据。
- drainTo:一次性获取所有数据。
4、网络编程与网络框架
1、TCP
- TCP是一个可靠的面向连接的协议,UDP是不可靠的或者说无连接的协议。
- 三次握手:
第一次握手,建立连接,客户端发送连接请求报文段,等待服务端确认;
第二次握手,服务端收到客户段的请求报文段,进行确认,然后发送给客户端;
第三次握手,客户端收到服务段返回的报文段,再向服务端发送报文段,发送完毕后,TCP三次握手完毕。 - 四次挥手:
第一次挥手,客户端向服务端发送一个报文段,表示客户端没有数据要发送给服务端了;
第二次挥手,服务端收到客户端发送的报文段,返回一个报文段;
第三次挥手,服务端向客户端发送报文段,请求关闭连接;
第四次挥手,客户端收到服务端请求关闭连接的报文段,向服务端发送一个报文段,服务端收到后,连接关闭。 - HTTP有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚 空闲下来的连接而无须再次握手。
5、事件总线
1、EventBus四种ThreadMode:
- POSTING:默认的,哪个线程发布就在哪个线程运行。
- MAIN:世界处理会在UI线程执行。
- BACKGROUND:主线程发布的会切到子线程处理,若是子线程发布的直接处理。
- ASYNC:无论哪个线程发布,都新建子线程处理。
6、应用架构设计
1、MVP
-
契约接口用来存放相同业务的Presenter和View的接口,便于查找和维护:
public interface Contract { interface Presenter{} interface View{ } }
2、MVVM
-
DataBinding动态更新与双向绑定
继承BaseObservablepublic class User extends BaseObservable{ private String userName; //使用@Bindable注释,产生BR.XXX @Bindable public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; notifyPropertyChanged(BR.userName);//通知某个变量发生了变化 } }
使用ObservableField
public class User { public ObservableField<String> name=new ObservableField<>(); } //赋值 user.name.set("user "+count++); //取值 user.name.get();
使用Observable容器类
<data> <import type="android.databinding.ObservableMap"/> <import type="com.czd.binding.Keys"/> <variable name="map" type="ObservableMap<String,Object>"/> </data> public class Keys{ public static final String name = "name"; public static final String age = "age"; } ActivityMainBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); final ObservableMap<String, Object> map = new ObservableArrayMap<>(); map.put("name", "sunzxyong"); map.put("age", 22); mBinding.setMap(map); mBinding.btn.setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(android.view.View v) { map.put("name","hello"); map.put("age",20); } });
双向绑定:
现在假设一种情况,当你更换成EditText时,如果你的用户名User.name已经绑定到EditText中,当用户输入文字的时候,你原来的user.name数据并没有同步改动,因此我们需要修改成:<layout ...> <data> <variable type="com.example.myapp.User" name="user"/> </data> <RelativeLayout ...> <EditText android:text="@={user.name}" .../> //关键 </RelativeLayout> </layout>
网友评论