Sxf-Blog


  • 首页

  • 归档

windowSoftInputMode

发表于 2020-01-17

windowSoftInputMode

  • adjustPan:输入框的高度高于键盘弹出的高度,那么软键盘弹出后页面不会顶上去,如果输入框的高度会被软键盘遮挡,那么软键盘弹出时,会将布局整体往上移动,直到输入框显示出来为止,值得注意的是:该移动方法并不会压缩布局,而是直接上移,不进行布局压缩,类似于整体布局scrollTo()操作
  • adjustResize :这个情况有点复杂,移动会压缩布局,也就是键盘弹出后,整体的页面显示View会被压缩,压缩的高度就是软键盘的高度

    • 假如输入框的高度大于软键盘的高度时:(布局底部View,时根布局最下部的View,要么是LinearLayout(child0有layout_weight=1表示)),要么RelativeLayout父布局有aliprentBottom
    • 点击输入框,底部会顶上去,不管输入框高度是否大于软键盘高度,前提是底部的VIew会顶上去
    • LinearLayout为父布局时,底部以上的都会弹起

picture

picture

* RelativeLayout为父布局时,只有底部的会弹起,底部到键盘之间的遮挡控件不会上移,除非是跟底部有某种约束关系的View,比如aboveUp:bottomView
* 虽然会压缩控件的布局,但是内部设置的margin还是真实存在的不会压缩,显示不下会往上顶

picture
picture

  • adjustNothing:永远不会对父布局进行弹出或者是移动改变父布局

  • adjustUnspecified:类似adjustPan

OverScroll&&VelocityTracker

发表于 2020-01-10

参考:

  • 让控件如此丝滑Scroller和VelocityTracker的API讲解与实战——Android高级UI

  • Android应用开发之自定义View触摸相关工具类全解

  • VelocityTracker简介

OverScroll && VelocityTracker

其实这两个类都是滑动的辅助工具类,其并不是内部主动调用滑动的,滑动需要自己进行scrollTo()或者scrollBy()的移动,其内部会有回调
对应的x,y坐标,和滑动的距离,和当前的位置,然后我们根据其返回的应该滑动到的位置进行滑动处理

OverScroll API

  • overScroll.fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY, int overX, int overY)
    滑动fling时触发调用

    startX:       fling的起始X位标
    startY:       fling的起始Y位标
    velocityX:  X轴的滑动速度
    velocityY:    Y轴的滑动速度
    minX    :    fling的最小的X坐标,也就是left的临界点:画布向左滑动,dexIndex的值是正的,向右滑动dexIndex的值是负的
                所以如果你画布一进去就是从最右边开始绘制,也就是需要向右滑动查看其余的数据,那么这个值就是你总需要滑动的x的负数
    maxX    :    fling的最大的X坐标,也就是right的临界点,如果你一开始就在最右边,也就是画布没移动的时候,最初位置的时候,那么max就是0
    minY    :    同minX一样
    maxY    :    同maxX一样
    overX    :    就是滑动到X临界点后可以回弹的距离,也可以理解为缓冲区
    overY    :    就是滑动到Y临界点后可以回弹的距离,也可以理解为缓冲区    
    

picture

  • overScroll.springBack(int startX, int startY, int minX, int maxX, int minY, int maxY);
    结合fling()使用,是做一些回滚操作,也就是回滚到设置的正确临界点,一般是讲fling的overX的距离回滚到正确的临界点

    startX:        回滚的起点X,一般是getScrollX()
    startY:    回滚的起点Y,一般是getScrollY()
    minX  :        这个就是正确的最小的临界点坐标X,
    maxX  :        这个就是正确的最大的临界点坐标X    ----》正常startX是大于maxX或者是小于minX,才会有回滚效果
    minY  :     同上
    maxY  :        同上
    
  • overScroll.startScroll(int startX, int startY, int dx, int dy)
    移动多少位置

    startX:        移动点当前坐标
    dx      :        移动的距离
    

原生View方法

前面也说了以上都是协助滑动的方法,并不能让View滑动,View滑动还需自己通过ScrollTo()或者scrollBy(),这种滑动需要计算好别越界处理。但是有一个方法是内部View原生
计算好的封装方法:overScrollBy();

  • overScrollBy(int deltaX, int deltaY,int scrollX, int scrollY,int scrollRangeX, int scrollRangeY,int maxOverScrollX, int maxOverScrollY,boolean isTouchEvent)
    其内部就是执行的scrollBy,通过onOverScrolled()的回调函数,但是在执行之前其对临界点判断做了处理

    deltaX: 滑动的距离,这里需要注意,移动的是画布,所以,deltax为负数才是画布往右移动,通过onTouchEvent拿到的右移动手势dex是正的,所以需要取反一下
    scrollX: 当前已经移动了多少距离getScrollX();
    scrollRangeX: 可以滑动的总距离,这个可以根据你是否需要overScroll,来决定是正常临界点还是多一点,同上边的,如果是右边起步,那么该值就是负数,以下会有代码解读
    maxOverScrollX:这个值比较奇怪,看底部代码
    isTouchEvent:是否是事件拖动时调用,也就是是否是onMove是调用

picture

onOverScrolled(int scrollX, int scrollY,boolean clampedX, boolean clampedY)

View的回调重写方法:overScrollBy()方法执行的内部回调函数,
scrollX : 移动的距离、New X scroll value in pixels、、也就是最新的移动后的X轴上的距离  getScrollX()
clampedX: x轴上的移动位置是否完成,也就是有没有到达上边overScrollBy()函数设置值的回调

        @Override
        protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
            int oldScrollX = getScrollX();
            int oldScrollY = getScrollY();
            setScrollX(scrollX);
            onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);

        }    

picture

onScrollChanged(int l, int t, int oldl, int oldt);

每次内部调用scrollTo()、scrollby()方法时都会执行该onScrollChanged()方法,内部函数会返回当前坐标和上一次坐标,从而view会判断是往哪个方向移动

VelocityTracker

  • 介绍参考:

    VelocityTracker简介

    ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
    scaledTouchSlop = viewConfiguration.getScaledTouchSlop();
    scaledMaximumFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
    scaledMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
    velocityTracker = VelocityTracker.obtain();//创建方式
    velocityTracker.addMovement(event);//事件手势传入
    velocityTracker.computeCurrentVelocity(int units, float maxVelocity);//设置滑动的速度单位,units=1000,maxVelocity=scaledMaximumFlingVelocity表示1s内滑动的像素的最大值是scaledMaximumFlingVelocity
    velocityTracker.getXVelocity();//获取x轴上的滑动速度
    

computeScroll()

         /**
          * Called by a parent to request that a child update its values for mScrollX
          * and mScrollY if necessary. This will typically be done if the child is
          * animating a scroll using a {@link android.widget.Scroller Scroller}
          * object.
          */

也就是通过对象Scroller或者是其子类OverScroller执行需要改变ScrollX\scrollY的操作时,会被调用,会被回调
,so,上边的startScroll()\fling()\springBack()等Api的真时调用滑动的方法都是在computerScroll()的回调函数中做的

picture

Path&Canvas

发表于 2020-01-07

Canvas api

  • drawArc()绘制弧形以Rect 的中心为圆点,90度方向为0度开始绘制弧形
//center 绘制的弧形是否经过圆点
canvas.drawArc(@NonNull RectF oval,float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)
  • canvas.drawTextOnPath(String str,Path path,float startAngle,float sweepAngle,Paint paint)

picture

Path api

  • 参考文献

Path常用方法解析

  • line to(x,y)
  • move to(x,y)
  • arcTo(Rect rect,float startAngle,float sweepAngle )//绘制弧形以Rect

  • close()//闭环,最后的path的终点会连接到其实点形成个闭环

  • arcTo(Rect rect,float startAngle,float sweepAngle,boolean forceMoveTo )//绘制弧形以Rect,forceMoveTo是否开始前画笔moveTo到Rect的起点位置

    绘制圆弧,若forceMoveTo为false,则用法和arcTo(RectF oval, float startAngle, float sweepAngle)一样,
    绘制圆弧之前不会移动(moveTo)path画笔位置。若为true,先强制调用moveTo移动path画笔至圆弧起点,再绘制圆弧。
    ps:如果调用arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)方法之前没有对path进行任何操作,
    则forceMoveTo设置true或false效果都和设置true一样
    
    //也就是传false后即使你调用前先执行了moveTo()某个位置,但是不生效,传true,其强制会在drawPath之前moveTo到Rect的起点,即使你在前moveTo()的xy轴不是Rect的起点,其还是会移动到起点的位置
    
  • quadTo(float x1,float y1,float x2,float y2)//绘制二阶贝塞尔曲线 控制点坐标(x1,y1)终点坐标(x2,y2)

    • 贝塞尔曲线:正规的正弦函数线,取三个点连接然后寻找三个等比的点开始绘制曲线

    • 贝塞尔曲线:需要有起始点,顾在path绘制之前需要调用move To()方法来设置一个画笔的起点

      必须要理解掌握的贝塞尔曲线(原创)

  • cubicTo(float x1,float y1,float x2,float y2,float x3,float y3)//绘制三阶贝塞尔曲线 控制点坐标(x1,y1),(x2,y2),终点坐标(x3,y3)

    • 同上也需要设置起始点坐标,其实起始点坐标就是贝塞尔起点的一个点
  • rXxxTo()//r是relative想对的意识,和上边直接moveTo(),quadTo(),lineTo(),cubicTo(),不一样的是,其绘制内部的参数坐标是相对于画笔当前点的坐标,并不是画布左上角0,0的坐标

    rMoveTo()
    rQuadTo()
    rLineTo()
    rCubicTo()
    

  • 以上Path的单一调用

    Path的结合调用addXxx()

  • 添加另一段path,addPath的时候,其是会先移动到该线的起点位置然后开始绘制该线,所以可以理解为是相互独立的几条path

  • addArc(RectF rect,float startAngle,float sweepAngle);
  • addCircle(float x,float y,float radius,Direction dir)//dir是线的闭合方向,CW顺时针方向,CCW逆时针方向
  • addOval(RectF rect,Direction dir)//在矩形RectF范围内绘制一个椭圆形状
  • addRect(RectF rect,Direction dir)//绘制矩形–绘制起点为左上角
  • addRoundRect(RectF rect,float rx,float ry,Direction dir)//绘制四个角圆角大小相同的矩形,CW顺时针方向,起点位置左下角,CCW逆时针方向,起始点左上角

    picture

  • addRoundRect(RectF rect,float[] radii,Direction dir)//radii 的长度必须是8,也就是4个角分别的横轴圆角半径x,纵轴圆角半径y,起点位置同上

  • addPath(Path path,float dx,float dy)//dx,X轴平移的距离,dy Y轴平移的距离
  • addPath(Path path,Matrix matrix)//Matrix 3x3矩阵、xyz,可以设置旋转,偏移,翻转等
  • close();//封闭当前path,连接起点和终点
  • reset();//清空path上直线、曲线、保留填充设置
  • op(Path path,Op op)//当前path和这个path的进行布尔运算,交集,并集,异或,
  • setLastPoint(float dx,float dy)//修改path的最末点的坐标
    ###PaintMeasure

  • 测量path的长度,可以得到path的总长度和path上某一点的坐标//getPosTan(),getLength();

  • getSegment(float startId,float stopId,Path path,boolean startWithMoveTo)//裁剪对应得path,以传入的path绘制裁剪的部分
  • getPosTan(float distance,float []pos,float []tans)//distance:该path上距起点的距离,pos,传入数组,接受path上该点的坐标,tans:返回该点的切线坐标
  • getLength();//返回该Path的总长度

    mPathMeasure.getPosTan(mPathMeasure.getLength() * percent, pos, null);
    

StringBuffer & StringBuilde

发表于 2019-12-20

参考

图析:String,StringBuffer与StringBuilder的区别

String

  • 字符串广泛应用 在Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

  • 需要注意的是,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,下方图解

pic

当每次修改String的值时,堆内存会重新指向一个新的地址对象。这里有以下关系

String a="world";<---->String a=new String("world");
对a进行修改后其实是
a="hello world";<---->a=new String("hello world");

  • 还有需要注意的

String a="hello"—在堆中创建了一个String对象

a+"world"—-在堆中创建了一个World的对象(new String(“world”))

a="hello world"—在堆中创建了最终的生成对象(new String(“hello world”))

StringBuilder & StringBuffer

  • 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

  • StringBuilder 线程不安全的,也就是不可以多个线程同时操作,除非加同步锁,单个线程推荐使用,速度比StringBuffer快。(不能同步访问)

  • StrinfBuffer 线程安全的,也就是可以多个线程同时使用,因为是内部有同步锁,所以运行速度没有StringBuffer快

线程安全和线程不安全

  • 牵涉对象调用有

前者线程不安全 后者线程安全

StringBuilder StringBuffer

ArrayList Vector

HashMap HashTable

  • 线程不安全意思是多线程操作可能会出现问题,当A线程和B线程同时访问并修改该对象时,会产生对应问题。比如:

假设A和B同时去不同ATM上取同一张卡的1000块钱,如果是线程不安全,那么A和B可以同时取到1000块钱(两人赚大发啦),而如果线程安全呢,就只有一个人能取出来1000块钱。

  • 非线程安全!=不安全

  • 使用场景:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

  • 有人在使用过程中有一个不正确的观点:我的程序是多线程的,不能使用ArrayList要使用Vector,这样才安全。

非线程安全并不是多线程环境下就不能使用。注意我上面有说到:多线程操作同一个对象。注意是同一个对象。

  • 同步锁synchronized

  • 同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    • 同步代码块

      public void test() {
          同步代码块:
          synchronized(同步锁)
          {
               //方法体内容
          }
          }
      
    • 同步方法

      `public synchronized  void test(){
      
           //方法体
      
          }`
      
  • 静态代码块&代码块(非静态代码块)–Java::main()函数使用

    static{
        //初始化 static 的变量或者...
    }
    
    {
        //代码块
    }
    
  • 执行顺序:父类静态代码块>子类静态代码块>父类代码块>子类代码块>自身构造方法

    静态代码块是在构造方法前执行,不需要该类创建对象,,代码块需要创建实例对象后才会首先执行

onSavedInstanceState()&& onRestoreInstanceState()

  • 参考 onSavedInstanceState()和onRestoreInstanceState()理解
  • 执行时间:

    onSaveInstanceState()会在onPause()或onStop()之前执行
    
    onRestoreInstanceState()会在onStart()和onResume()之间执行。
    
  • 当应用遇到意外情况(内存不足,用户直接按home键)由系统直接销毁一个Activity时,onSaveInstanceState()就会调用,但是当用户主动销毁activity,如按back键,或直接执行finish(),这种情况下onSaveInstanceState()就不会执行,因为这种情况下,用户的行为决定了不需要保存Activity的状态。
  • 那么onRestoreInstanceState()会跟onSaveInstanceState()成对出现吗? 答案是不会成对出现,onSaveInstanceState()需要调用的时,activity可能销毁(可能在栈底,无感知创建销毁),也可能没有销毁,只有在activity销毁重建的时候onRestoreInstanceState()才会调用。

在onSaveInstanceState()中默认情况下具体干些什么?

默认情况下默认会自动保存Activity中的某些状态,比如activity中各种UI的状态,因此在activity被“系统”销毁和重建的时候,这些Ui的状态会默认保存,但是前提条件是Ui控件必须制定id,如果没有指定id的话,UI的状态是无法保存的(比如:EditText)。

所以有时候为了某种场合记录数据,可以手动调用onSaveInstanceState()方法

  • 一般横竖屏切换时Activity的生命周期会重新走,也就是Activity会重新创建,无感知创建,一般会在清单文件加上configChanges属性,来使横竖屏切换时Activity的生命周期不会重新走

configChanges 解析

  • 参考

关于Android Activity的configChanges属性的用法,设置屏幕旋转时activity不重新创建
android:configChanges 横竖屏切换的生命周期

  • android中的组件Activity在manifest.xml文件中可以指定参数android:ConfigChanges,用于捕获手机状态的改变。在Activity中添加了android:configChanges属性,在当所指定属性(Configuration Changes)发生改变时,通知程序调用onConfigurationChanged()函数。

  • 在清单文件Activity节点下可以添加android:configChanges属性,指定属性发生改变时,调用Activity 的onConfigurationChanged()方法,不会创建新的Activity
    设置方法:将下列字段用“|”符号分隔开,例如:”keyboardHidden|orientation|screenSize”

“mcc“ 移动国家号码,由三位数字组成,每个国家都有自己独立的MCC,可以识别手机用户所属国家。
“mnc“ 移动网号,在一个国家或者地区中,用于区分手机用户的服务商。
“locale“ 所在地区发生变化。
“touchscreen“ 触摸屏已经改变。(这不应该常发生。)
“keyboard“ 键盘模式发生变化,例如:用户接入外部键盘输入。
“keyboardHidden“ 用户打开手机硬件键盘
“navigation“ 导航型发生了变化。(这不应该常发生。)
“orientation“ 设备旋转,横向显示和竖向显示模式切换。
“fontScale“ 全局字体大小缩放发生改变
screenSize:设备的屏幕的尺寸信息发生了改变,旋转屏幕时也会触发这个,但是这个值是API13之后才有的,因此13之前的设备没有这个。
  • 不设置config属性时,当页面状态改变时,会重新创建Activity,会重新走Activity的生命周期

Fragment生命周期之ViewPager懒加载

发表于 2019-12-20

Fragment生命周期

  • onCreateView() :每次加载Fragment的时候会执行该方法、配对的生命周期是: onDestroyView()

  • onCreateView() :onCreateView()方法执行完后进行执行,代表View绘制完成

  • onResume() :该Fragment被执行了show操作、或者是该页面从后台显示到前台,或者是其依赖的Activity的生命周期的改变;那种ViewPager+Fragment的那种,ViewPager切换的时候,其Fragment是不执行onResume和onPause的,因为其Activity的声明周期没有改变,而且也没有执行hide和show操作,其移动就是将该Fragment进行了移动,并没有移出到后台?所以没有执行Fragment的生命周期、、、、、其实Fragment的生命周期都是根据Activity来的,除了自己的创建、尤其是onResume和onPause都是根据Activity的声明周期的onResume和onPause、、、、真正的判断实现的方法有两个:setUserVisibleHint()和onHiddenChanged()—针对show\hide的Fragment

picture

ViewPager+Fragment “预”加载方式解析

  • viewPager.setOffscreenPageLimit(x): 不设置默认ViewPager会预加载相邻的1个Fragment,当切换Fragment时之前相邻的Fragment会执行onDestroyView()将Fragment销毁,当再次切换到该Fragment时其会重新生成该Fragment,也就是重新调用onCreateView()方法,也就是每次点击非相邻的Tab会重新加载渲染一次Fragment

  • viewPager.setOffscreenPageLimit(n):设置值后,一般设置fragmentList-1,这样其切换Fragment的时候所有的Fragment不会重走生命周期,也就是不会走onDestroyView()和onCreateView()进行Fragment的重复创建,这种的话刚启动加载第一个Fragment的时候,其余的都会执行onCreateView()、、onCreateView()方法进行预加载,当然这种当Activity执行生命周期onPause时,所有的都会执行onPause

  • 其实ViewPager就是ViewGroup就是一个View的容器,类似于容器也就是说不管内部有多少View其都会“一次全部”加载到容器内,也就是不管现在在屏幕中的还是没有在屏幕中显示的View,其已经全部加载到该容器中了,不会因为你不在该屏幕上显示就说你没有加载到ViewGroup中,排除外来因素(手动进行ViewGroup的移出添加、或者是手动调用内部View的相关方法),勉强解释为什么setOffscreenPageLimit(n),切换Fragment的时候,不走Fragment的声明周期

ViewPager+Fragment “懒”加载方式解析

Fragment的是否显示在前台方法(onResume\onPause在上边的那种情况代表不了)

  • setUserVisibleHint(boolean isVisibleToUser): isVisibleToUser为ture表示该Fragment在前台展示,isVisibleToUser为false表示该Frament没有在前台展示(并不代表其跑到了后台,如果都是这样那么Fragment的onResume和onPause就可以判断了),在ViewPager+Fragment模式下设置了setOffscreenPageLimit(n)并且切换Tab的时候一样会改变该值

  • setUserVisibleHint: 该方法有可能会被多次回调,也就是说在判断是否显示在前台前,最好还是判断一下该View是否加载完成,然后在决定是否进行网络请求;当然为了防止每次点击都加载数据,还可以添加个别的变量isFirst来判断是否是第一次加载

  • 这种一般设置在BaseFragment中,向外部抛出abstract方法

    public abstract void fragmentIsVisible(boolean isVisibleToUser);

  @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true;
isFirstCreate = true;
doLazyLoad();
}

..............................
    /**
 * 返回True  表示当前View是否加载到窗口、就是是否是当前窗口展示
 *
 * @param isVisibleToUser
 */
    @Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
    //该方法有可能会调用多次、包括有可能View还没创建就进行调用、、、防止重复加载、防止View还没创建就进行View的调用操作
    isViewVisible = true;
    doLazyLoad();
    } else {
    isViewVisible = false;
        }
     }

......................................

   //需要区分是否是第一次加载、防止每次切换都进行数据的调用  更新
private void doLazyLoad() {
    if (isViewCreated && isViewVisible) {
        if (isFirstCreate) {
        fragmentIsVisible(true);
        isFirstCreate = false;
        }
    } else {
        fragmentIsVisible(false);
    }
}

ViewPager 的进制左右滑动

  • dispatchTouchEvent(MotionEvent ev):不变还是之前的 return super.dispatchTouchEvent(ev)-支持向下分发

  • onInterceptTouchEvent(MotionEvent ev):改变return false;不进行事件拦截,进行向底部View传递

  • onTouchEvent(MotionEvent ev) :改变 return false:底部View没有响应消费的话,事件回传过来自己也不进行消费,再往外传,不进行滑动响应

picture

事件分发

发表于 2019-12-20

事件传递的过程分析

  • 事件传递 从最外层父View将事件 传递到 内部View 首先传递前 判断自己的dispatchTouchEvent 方法是否支持分发,支持分发的话,会传递自己View的onInterceptTouchEvent 方法,看自己是否进行拦截处理,若拦截则会传递到自己的onTouchEvent,若不拦截会传递到子View的dispatchTouchEvent 方法进行处理,内部处理流程和上一层View的处理流程一样: dispatchTouchEvent–>onInterceptTouchEvent–>onTouchEvent

DispatchTouchEvent之return

  • false: 返回给父View的OnTouchEvent事件

  • true :停止传递分发、事件不传递、也就是不会有onTouchEvnet事件之说,因为onTouchEvent事件,执行的时候是事件全部传到最底部View后才会执行,最底部的onTouchEvent不消费的话,才会向上一级的父View的onTouchEvent传递(也就是才会执行到父View的该方法、、当然除非是中间拦截了,才会不向下传递、直接执行自己的OnTouchEvent方法)

  • super.DispatchTouchEvent之return:支持分发(也就是该事件已经产生、会进行处理、直到有人将其消费、如果返回上边的,该事件就相当于没有产生,直接返给窗体View、直接被扼杀在摇篮里)、会传递到该View的onInterceptTouchEvent方法,由该方法来处理看是否继续向下传递;

onInterceptTouchEvent之return

  • false: 不拦截传递给下一层子View进行事件分发方法:dispatchTouchEvent() 流程相同继续走。
  • true: 拦截事件,不向下传递事件,会传递给自己的onTouchEvent()事件处理响应。
  • super.onInterceptTouchEvent(): 等同于true,即进行拦截,交由自己的onTouchEvnent进行处理

onTouchEvent()之return

  • false: 不消费、传递到上一层的View的onTouchEvnet?
  • true : 消费,自己执行onTouchEvnet方法
  • super.onTouchEvnet(): 等同于false 传递到上一层父View的 onTouchEvent进行处理

    event.getPointerCount()//判断手指
    MotionEvent.ACTION_XXX
    MotionEvent.ACTION_UP//需要全部手指全部离开屏幕才会调用
    MotionEvent.ACTION_POINTER_UP//当存在多个手指触碰时,抬起一指就会执行该方法
    MotionEvent.ACTION_POINTER_DOWN
    

GestureDetector 手势管理之OnGestureListener

  • void onShowPress(MotionEvent e);

  • boolean onSingleTapUp(MotionEvent e);

  • void onLongPress(MotionEvent e);

  • boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

接收方式

  • onTouchEvent 内部调用—mGestureDetector.onTouchEvent(event);

子View控制父View拦截

getParent().requestDisallowInterceptTouchEvent(true);

  • true:表示父View对事件进行拦截无效例如:onInterceptTouchEvent内部对事件down、move返回true,也是不起作用的

  • false:表示父View对内部子View的拦截是有效的

总结

  • 所有的事件都是先往最底层传递的、最底层没有消费时才会一层一层的往外层传到各自的onTouchEvent()方法中,但是事件首先都是会执行DispatchTouchEvent方法进行分发传递到下一层的,其中会走每一层的:DispatchTouchEvent和onInterceptTouchEvent方法判断是不是需要拦截,不拦截就继续往下一层走,所以onInterceptTouchEvent的Down事件是都会走的,:包括MOVE事件、UP事件,当最外层的事件把Down拦截了,下边的就不会走了,当没有拦截,会一层一层走,,当最底层有方法表示

    `getParent().requestDisallowInterceptTouchEvent(true);`  
    

    谁都不能拦截我的事件,那么事件就不会往上在传递到外层的move、up等事件了就会到该处消费结束!!!也就是说这个时候onInterceptTouchEvent的MOVE事件和UP事件都不会走了(对应的方法都不走了),其他情况还是每一层新的事件都会进行传递所以会出现那种:不拦截Down事件拦截Move事件的情况,,所以如果底部有拦截但是外层也想获取点击的坐标位置:就可以在onInterceptTouchEvent方法的down事件中进行获取点击的坐标位置、

picture

事件分发传递

发表于 2019-12-20

Android 获取屏幕高度注意事项

  • 在Activity里获取WindowManager
    WindowManager windowManager = getWindowManager();
    DisplayMetrics displayMetrics = new DisplayMetric();
    Display defaultDisplay = windowManager.getDefaultDisplay();
    defaultDisplay.getMetrics(displayMetrics);
    int screenHeight=displayMetrics.heightPixels;
    
  • 在非Activity对象实例中获取WindowManager-通过getSystenService(Context.WINDOW_SERVICE)
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    

高度区域分布图

  • 通过DisplayMetrics获取的屏幕高度是屏幕全部界面的高度,但是除了有虚拟键盘的手机(例如:华为)
    • displayMetrics.heightPixels=View绘制区+系统标题栏高度+状态栏高度
    • 华为手机displayMetrics.heightPixels=全部界面高度-底部虚拟键盘高度,通过displayMetrics.heightPixels获取的屏幕高度 其已经是减去软键盘的高度了(例如:一个带有虚拟键盘的华为手机分辨率为720*1280–那么其displayMetrics.heightPixels获取的值1208,通过计算其虚拟键盘的高度正好是72)

NestedScroll嵌套滑动

  • 主接口:NestedScrollingParent、NestedScrollingChild
  • 处理工具类:NestedScrollingParentHelper、NestedScrollingChildHelper
  • 原生实现的Parent
    • CoordinatorLayout
    • NestedScrollView
  • 原生实现的Child
    • RecyclerView

CoordinatorLayout的基本使用–layout_behavior

  • 使用需要和AppBarLayout(继承LinearLayout、@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class))
  • layout_behavior:也就是可以将控件大小绘制出超出屏幕的部分,超出的大小是基于设置的layout_behavior决定的;故也就是使用的默认的Behavior–AppBarLayout,而且CoordinateLayout是继承ViewGroup内部没有设置所谓的below、等所以用AppBarLayout(因为其是继承LinearLayout的)

AppBarLayout标签的使用layout_scrollFlags

  • scroll 该View会响应Scroll事件
  • exitUntilCollapsed 该View上拉的时候,这个View会跟着滑动直到折叠。

CollapsingToolbarLayout标签使用layout_collapseMode

  • 是一种专门针对ToolBar设计的布局
  • 一般使用时结合AppBarLayout–当然AppBarLayout外部肯定是CoordinateLayout
  • CollapsingToolbarLayout在设置AppBarLayout的标签exitUntilCollapsed后,在上拉时会随着滚动直到折叠-而折叠的高度就是CollapsingToolbarLayout内部设置ToolBar的高度,内部设置的其余View是不会有折叠效果的,故一般内部就是配合ToolBar的使用
  • layout_collapseMode-pin 在滑动的时候该View会悬停在原地-直到CollapsingToolbarLayout的低部碰到该View的底部才会跟着一块滑
  • layout_collapseMode-parallax 在滑动的过程中该View会是渐变效果-视觉特差效果

NestedScrollView与RecyclerView嵌套滑动

  • 简单的拦截就是setNestedScrollingable(false);
  • 这时刚开始滑动的是父布局控件NestedScrollView,RecyclerView并没有获取事件、或者说不消费事件(recyclerView.setNestedScrollingable(false)),但这时如果需要根据NestedScrollView的滑动距离来判断什么时候把事件交给RecyclerView,也就是RecyclerView开始消费事件(setNestedScrollingable(true));
  • 如果需要刚开始滑动NestedScrollVIew-根据ScrollView的了解需要布局操出屏幕。跟RecyclerView和ListView是不同的,故底部RecyclerView控件需要设置其高度屏幕高度-需要滑动到要给RecyclerView手势时的距离高度、也就是悬停后的高度
  • 一开始渲染View就需要设置其高度,但是通过getHeight在刚渲染的时候拿不到,但有一个方法可以获取高度

    linearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        //内部可以获取到该父布局和布局树-其内部子View的高度
        });
    
  • 判断手势超过临界值是向上滑动还是向下滑动–一般都是通过临界点的滑动值–例如滑到300就把手势交给子View,这时把ViewGroup的高度设置305这样当滑到301的时候判断滑动距离大于300就知道是向上滑动的

OnTouchEvent()

  • 我们在ViewGroup 2 的onTouchEvent 返回true消费这次事件
    红色的箭头代表ACTION_DOWN 事件的流向
    蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

  • 我们在View 的onTouchEvent 返回true消费这次事件
    红色的箭头代表ACTION_DOWN 事件的流向
    蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

  • 首先看下ViewGroup 的dispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent,然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return true和false 都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent,记住这一点。
    那么对于View的dispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结。return false 是回溯会父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent 处理呢,那只能return super.dispatchTouchEvent,View类的dispatchTouchEvent()方法默认实现就是能帮你调用View自己的onTouchEvent方法的。

  • 说了这么多,不知道有说清楚没有,我这边最后总结一下:

    • 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
    • ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
    • ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
    • View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
  • 自己消费,终结传递。——->return true ;

  • 给自己的onTouchEvent处理——-> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
  • 传给子View——>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
  • 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent——->return false;
    注: 由于View没有子View所以不需要onInterceptTouchEvent 来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。

  • 总结(从事件的方法函数调用顺序开始):

    • dispatchTouchEvent是事件分发:return true/false-表示都是直接消费,不往下分发,但是会直接传递给父级的onTouchEvent()方法,return super.dispatchTouchEvent()-是向下调用:activity的会调用ViewGroup的dispatchTouchEvent,如果viewgroup的也是返回super,那么ViewGroup的super其实是调用自己的onInterceptTouchEvent()方法-的返回值,如果onInterceptTouchEvent返回true表示给自己的View消费-会调用ViewGroup的onTouchEvent方法,如果onInterceptTouchEvent()-return false/super,那么是调用的是View的dispatchTouchEvent(),如果view的该方法返回true、false直接消费不会传给自己的onTouchEvent(),如果返回super,那么其实调用的是自己的onTouchEvnet方法,
    • onTouchEvent 方法 return true 表示消费事件,其down move up会传递到该View并停止,,如果返回false,,那么down会传递过来,以后的move up 事件就不会再传递给他,,view的onTouchEvent返回false 那么会向父ViewGroup的onTOuchEvent传递

参考文献

事件分发经典参考
Android完美获取状态栏高度、标题栏高度、编辑区域高度的获取
使用CoordinatorLayout打造各种炫酷的效果

内存泄漏案例以及优化

发表于 2019-12-20

Android内存泄漏

  • 页面A即将关闭、但是页面或者是对象B持有页面A的引用,这时就会导致页面A的对象多为Context无法回收,这时就会导致内存泄漏

    • 一般体现是:单例模式创建静态变量时-持有页面的context,也常用于MVP-Persenter。

      • 单例模式处理一般context获取其Application层面的context
      • 使用Application层面的context并不会因为持有context是系统级别的不会被回收,:回收是对象A回收时,没有其他对应引用A
    • 使用内部类对象时没有对内部类引用进行弱引用处理。

      • 内部类在创建对象时内部会获取外部类的引用、这样其才可以不限制使用外部类的变量或者是方法(类与类的作用域方法变量是分开的即使是内部类与外部类),所以在创建内部类时一般会用静态内部类,因为静态内部类创建时不包含外部类的引用
    • 内部类经典案例Handler

      • 不规范的handler

picture

`这里的handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。`
  • 正确的做法是:

picture

`该案例也说明了两点:1、内部类创建时静态内部类操作外部类对象时需要外部类对象,这时对外部类对象进行弱引用    
                    2、内部类对象例如Handler正常第一种创建、其在使用的时候就是个内部类内部可以调用外部的对象就是持有外部的引用,这也是为什么会要继承Handler创建对象的原因`

待续….

参考

  • Android——内存篇:Android中5种最常见的内存泄漏问题以及解决办法

  • java 内部类和静态内部类的区别

Nexus 用户权限管理

发表于 2019-12-20

Nexus 默认创建的用户

  • admin用户: 拥有对Nexus服务完全控制、默认密码admin123 ,包括对仓库的配置,创建,删除,更新,用户的账号权限登录管理等。
  • deployment用户:能访问nexus的主页,包含登录,浏览仓库,搜索,上传、更新,但是其不能更改Nexus和已有仓库的配置,不能创建和删除仓库,默认密码:deployment123。

Nexus 基本权限

  • UI:Basic UI Privileges:访问Nexus的页面必须的最基本的权限(登录Nexus的权限)。
  • UI:RepositoryBrowser:浏览仓库页面的权限(登录后浏览网页上仓库的权限)。
  • UI:Search:访问快速搜索栏以及搜索界面所需要的权限
    以上仅仅是可以登录Nexus的主页以及进入到内部仓库页面的基本权限,能不能查看仓库需要下边的权限
  • Repo:AllRepositories (Read):给予用户读取所有仓库内容的权限,不给予该权限,用户在登录Nexus的主页后看不到对应的仓库信息。
  • Repo:AllRepositories (Full Control): 给予用户可以控制仓库的所有权限,浏览,可以删除仓库的内容,更新仓库的内容,上传部署构建。

Nexus 创建特殊权限用户

  • 在Nexus的首页-Security-Users:可以创建新用户和查看已有的用户信息,点击没个用户可以在:Config中查看用户拥有的权限组、在Role Tree中查看对应的权限组的内部权限详情,可以在Config中对用户权限进行添加或者修改移除,此处添加用户权限只会让你在已有的权限组列表选择,如果当前已有的不符合你的需求,可以自己创建权限组,将自己单独需要的权限进行权限组的添加,这些只有admin才可以操作(拥有权限比较多)。

Nexus 自定义权限组

  • 以上的基本权限都是权限组:即都是由几个权限组成成一个,例如:AllRepositories (Full Control) 内部就包含:All Respositories(create)、All Respositories(delete)、All Respositories(read)、All Respositories(update)、All Respositories(view),对仓库内容的增删改查。
  • 创建自定义权限组:Nexus的首页-Security-Roles:添加Nexus Role 进行权限添加,此处添加的列表不仅有权限组列表还有单独的权限列表,创建完成后即可在上边提到的用户权限修改处进行自定义权限组的添加。

Nexus 已有的权限组

  • Nexus Anoymous Role:包含:UI_RepositoryBrowser、UI:Search 、基本的Nexus的浏览、用户密码修改,仓库类型、状态、快速搜索(没有登录权限),所以一般需要再添加UI:Basic UI Privileges,组合成新的权限组。
  • Nexus Deployment Role: 包含:Nexus Anoymous Role 和 UI:Basic UI Privileges,所以如果给予用户查看Nexus的权限一般会用这个。
  • 上边两个权限只是可以进到Nexus的页面进行查看,但是要想看到仓库内容还需要添加以下权限
  • All Repositories (Full Control): 上边已经介绍过了,增删改查仓库的内容(包含:All Respositories(read)和All Respositories(view)可以查看仓库的内容)。
  • All Respositories(read):只能查看仓库(包含read和view两个子权限)

Nexus 场景需求

  • 如果需要用户更新并且可以查看仓库内容,可以对内容进行修改删除(也就是已经存在的deployment用户):Nexus Deployment Role 和 All Repositories (Full Control)
  • 如果需要用户更新,并不需要查看可以添加:All Repositories (Full Control)
  • 如果需要用户更新,查看仓库,但是并不需要删除仓库内容:自定义权限组:不添加All Respositories(delete)子权限即可
  • 如果需要用户只能查看仓库,不能对仓库进行任何修改:Nexus Deployment Role 和 All Respositories(read)

    Picture

  • 用户列表和权限

picture

  • 用户权限树

picture

  • 添加用户权限

picture

  • 创建自定义权限组

picture

picture

Handler&HandlerThread

发表于 2019-12-20

参考

  • 为什么要用HandlerThread,怎么用

Handler理解

  1. Handler一般用于进行线程间通讯,一般是子线程向主线程发送数据变动,更新UI操作。
  2. Handler的消息回调接收是是跟你创建Handler的线程绑定的,即你在那个线程创建的Handler,其不管在那个线程发送消息,最后回调的方法实现都是在你创建Handler的线程里完成。
  3. Handler跟Thread没有关系,其内部不能做耗时操作,但是可以发送延迟消息… …

    Handler机制

  • Message:消息,由MessageQueue统一队列,交给Handler处理。
  • MessageQueue:消息队列,用来存放Handler发送过来的Message,并且按照先入先出的规则执行。
  • Handler:处理者,负责发送和处理Message.
  • Looper:死循环,不断从MessageQueue中获取Message并执行。

    Looper

  1. Handler的创建时该线程中必须含有Looper对象。
  2. 在程序启动时,系统会先加载ActivityThread这个类,该类在main方法中会生成Looper对象:Looper.prepareMainLooper(),并随之创建消息队列,然后会调用Looper.loop()进行队列中的消息循环,故MainThread中可以直接创建Handler对象,发送消息。
  3. 非主线程是不自带Looper的,如果在子线程中进行Handler的创建需要自己调用Looper.prepare()创建Looper对象,不然会报java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()异常。注意:调用之前需要判断该线程是否有Looper对象,通过: Loopper.myLooper()来判断,不然会报java.lang.RuntimeException: Only one Looper may be created per thread异常。
  4. 获取主线程的Looper对象:Looper.getMainLooper()。

    Handler-Message&Runable

  • sendMessage():一般用于线程间消息传递
  • post(new Runnable):其并不是开启一个Thread,只是单纯的发送一个Null的消息,回调会在Runnable的实现run()方法只执行,一般会结合HandlerThread来使用。

    子线程创建Handler对象(子线程需要借助Looper处理)

  • 方式一:
public class MyHandlerThread extends Thread {
public static final String TAG = "MyHT";

public Handler mHandler = null;

@Override
public void run() {
    Log.d(TAG, "进入Thread的run");
    Looper.prepare();//looper创建
    mHandler = new Handler(Looper.myLooper()){
        @Override
        public void handleMessage(Message msg){
            Log.d(TAG, "获得了message");
            super.handleMessage(msg);
        }
    };
    Looper.loop();//因开始无限while(true)死循环,下边的代码不会再执行,mHandler.getLooper().quit(),可以结束Looper.loop循环
    ... ...
    //主线程的所有操作都在Looper循环体内部执行,故不会出现这种情况
}
}
  • 方式二(HandlerThread:为创建子线程Looper做了安全封装):

    //在MainThread中创建,HandlerThread其实就是在创建个带有Looper对象的子线程  
    //也不能这样说:其实就是创建了个Looper对象,只是在子线程中创建的Looper对象、、、之所以创建还不是因为Handler需要在子线程中使用
    //Handler handler =new Handler();//其实内部参数是需要传入一个Looper对象的
    
HandlerThread handlerThr=new HandlerThread("HandlerThread");
      handlerThr.start();//开启线程
      Looper looper = handlerThr.getLooper();
      Handler handler=new Handler(looper, new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              //该Handler是在子线程中创建的--所以其handlerMessage()回调仍然在子线程中--回调线程是个创建Handler的线程在一起
              //故这个回调里也不能更新UI
              //需要handler.sendMessage();才能走改回调

              //因为其已经创建过Looper.prepare()<--->Looper.loop(),顾Toast可以show;
              Toast.makeText(Main2Activity.this, "异步回调", Toast.LENGTH_SHORT).show();
              //Only the original thread that created a view hierarchy can touch its views
              //btnHandlerThread.setText("thread");
              return false;
          }
      });//该Handler就是在子线程中生命的Handler---并且 你不需要Looper.prepare();Looper.loop(),HandlerThread内部已经创建了Looper对象;
      .........  // UI操作
      .........
      .........
      if(handler!=null) {
          handler.post(new Runnable() {
              @Override
              public void run() {
                ///这个是主线程回调不是子线程-可以更新UI,,message.callBack=runable;\跟普通View.post(new Runable{})不知道有啥区别
                btnHandlerThread.setText("main-thread");
                Toast.makeText(Main2Activity.this,"异步请求",Toast.LENGTH_SHORT).show();
              }
          });
      }

Destory取消队列循环

  • sendMessage()方式进行消息传递:handler.removeCallBacksAndMessages(null)
  • post(runnable)方式:handler.removeCallBacks(runnable);

Handler 创建会持有外部类对象,造成内存泄漏

详解 Handler 内存泄露的原因

12
Sxf

Sxf

Nothing to say

17 日志
© 2020 Sxf
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4