美文网首页
《Android进阶之光》小结

《Android进阶之光》小结

作者: Dane_404 | 来源:发表于2019-03-20 08:36 被阅读0次

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动态更新与双向绑定
    继承BaseObservable

    public 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>
    

相关文章

网友评论

      本文标题:《Android进阶之光》小结

      本文链接:https://www.haomeiwen.com/subject/azaomqtx.html