Android开发艺术探索-第三章-View的事件体系

御弟哥哥 等级 661 0 0

3.1 View的基础知识

  • 位置参数

    top、left、right、bottom,在3.0之后增加了x、y、translationX、translationY.这里的所有参数都是相对其父布局来说的.
    下面是具体的含义表示

    Android开发艺术探索-第三章-View的事件体系

    View参数

    其中参数的关系为

     x = left + translationX
           y = top + translationY 
  • MontionEvent和TouchSlop

    MontionEvent代表着触摸事件封装的数据,包括常用的Action和位置参数等.如上面图示,注意函数getRaw*()是相对与屏幕的.
    TouchSlop表示滑动的最小常量.是常量(int),不是具体的类.获取方式为:

     ViewConfiguration.get(getContext()).getScaledTouchSlop() 
  • VelocityTracker,GestureDetector和Scroller

    VelocityTracker用于追踪手指在滑动过程中的速度,包括水平和垂直方向上的速度。
    速度计算公式:

     速度 = (终点位置 - 起点位置) / 时间段 

    速度可能为负值,例如当手指从屏幕右边往左边滑动的时候。此外,速度是单位时间内移动的像素数,单位时间不一定是1秒钟,可以使用方法
    computeCurrentVelocity(xxx)指定单位时间是多少,单位是ms。例如通过computeCurrentVelocity(1000)来获取速度,手指在1s中
    滑动了100个像素,那么速度是100,即100(像素/1000ms)。如果computeCurrentVelocity(100)来获取速度,在100ms内手指只是滑动了
    10个像素,那么速度是10,即10(像素/100ms)。
    VelocityTracker的使用方式:

     //初始化
           VelocityTracker mVelocityTracker = VelocityTracker.obtain();
           //在onTouchEvent方法中
           mVelocityTracker.addMovement(event);
           //获取速度
           mVelocityTracker.computeCurrentVelocity(1000);
           float xVelocity = mVelocityTracker.getXVelocity();
           //重置和回收
           mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用
           mVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用 

    GestureDetector用于辅助检测用户的单击、滑动、长按、双击等行为。GestureDetector的使用比较简单,主要也是辅助检测常见的触屏事件。
    作者建议:如果只是监听滑动相关的事件在onTouchEvent中实现;如果要监听双击这种行为的话,那么就使用GestureDetector。

    Android开发艺术探索-第三章-View的事件体系

    GestureDetector

    Android开发艺术探索-第三章-View的事件体系

    DoubleTabListener

    Android开发艺术探索-第三章-View的事件体系

    GestureListener

     //自定义的View,实现相关接口(onGestureListener,onDoubleTabListener)
           GestureDetector mGestureDetector = 
                   new GestureDetector(this/*context*/,listener/*onGestureListener*/);
    
           //function onTouchEvent(...)或onTouchListener的onTouch(...)中,直接返回
           return mGestureDetector.onTouchEvent(event) 

    更多使用参见[参考2]

3.2 View的滑动

  • layout

     public void layout (int l, int t, int r, int b) 

    参数都是相对与父布局.

     @Override
           public boolean onTouchEvent(MotionEvent event) {
               int rawX = (int) (event.getRawX()); //相对与屏幕的坐标
               int rawY = (int) (event.getRawY());
               switch (event.getAction()) {
                   case MotionEvent.ACTION_DOWN:
                       // 记录触摸点坐标
                       lastX = rawX;
                       lastY = rawY;
                       break;
                   case MotionEvent.ACTION_MOVE:
                       // 计算偏移量
                       int offsetX = rawX - lastX;
                       int offsetY = rawY - lastY;
                       // 在当前left、top、right、bottom的基础上加上偏移量
                       layout(getLeft() + offsetX,
                               getTop() + offsetY,
                               getRight() + offsetX,
                               getBottom() + offsetY);
                       // 重新设置初始坐标
                       lastX = rawX;
                       lastY = rawY;
                       break;
               }
               return true;
           } 
  • offsetLeftAndRight和offsetTopAndBottom

    使用方法同上几乎一致

     //直接在onTouchEvent中调用,替换上面的layout(...)部分
           offsetLeftAndRight(offestX);
           offsetTopAndBottom(offestY); 
  • LayoutParams

    这个方式在平时开发中应该使用的比较多.使用也是很简单,就是修改params的某些参数

     //ViewGroup.MarginLayoutParams layoutParams = 
           //               (ViewGroup.MarginLayoutParams) getLayoutParams();
           //LinearLayout.LayoutParams extends ViewGroup.MarginLayoutParams,
           //几乎所有的LayoutParms都是继承至
           //ViewGroup.MarginLayoutParams,
           //所以ViewGroup.MarginLayoutParams是通用的...
           LinearLayout.LayoutParams layoutParams = 
                                 (LinearLayout.LayoutParams) getLayoutParams();
           layoutParams.leftMargin = getLeft() + offsetX;
           layoutParams.topMargin = getTop() + offsetY;
           setLayoutParams(layoutParams);
           //requestLayout();效果和上面这一句一样 
  • ViewDragHelper

    ViewDragHelper的使用过程其实也是比较简单的,主要用户控制部分都在Callback中.CallBack中的函数比较多

    Android开发艺术探索-第三章-View的事件体系

    CallBack

    下面是一个简单的栗子:

     //初始化
           mDragHelper = ViewDragHelper.create(this/*要处理的ViewGroup*/, 
                          1.0f/*敏感度*/, new DragHelperCallback()/*前面说的Callback*/);
    
           //复写一些函数,代码几乎固定
           @Override
           public boolean onInterceptTouchEvent(MotionEvent ev) {
             final int action = MotionEventCompat.getActionMasked(ev);
             if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                 mDragHelper.cancel();
                 return false;
             }
             return mDragHelper.shouldInterceptTouchEvent(ev);
           }
           @Override
           public boolean onTouchEvent(MotionEvent ev) {
             mDragHelper.processTouchEvent(ev);
             return true;
           } 
  • ScrollTo和ScrollBy

    根据函数名称就知道这两个函数的区别,To是到具体的点,by只是与当前的偏移.
    这两个函数不是针对view本身,而是针对其内容,具体来说就是ViewGroup调用这两函数,是其内部的view在移动,view调用是其内容在动(TextView-->文本,ImageView-->图像)
    另一方面就是他的参数不同与其他,正数X往左,正数Y往上.原因查看这里, 如果想要移动View,就需要在她的parent上调用这函数,下面是个栗子

     //替换上文onTouchEvent中的layout(...)
         ((ViewGroup) getParent()).scrollBy(-offsetX, -offsetY); 
  • Scroller

    在以前都不知道有这个类,哎,基础不够诶.下面一个栗子说明

     //初始化,还可以使用插值器
         Scroller mScroller = new Scroller(mContext,interpolator/*插值器,可以不用*/);
    
         //View的computescroll()
         @Override
         public void computeScroll() {
             super.computeScroll();
             // 判断Scroller是否执行完毕
             if (mScroller.computeScrollOffset()) {
                 ((View) getParent()).scrollTo( mScroller.getCurrX(), mScroller.getCurrY());
                 // 通过重绘来不断调用computeScroll
                 invalidate();//很重要
             }
         }
    
         //启动
         mScroller.startScroll(startX,startY,dX,dY,duration); 

    本质上Scroller不能移动View,在我看来她同属性动画中的ValueAnimator是一样的,因为他们都只是按照某种插值器产生数值,需要自己把数值同移动
    相联系.

3.3 View的事件分发机制

  1. 事件分发过程的三个重要方法

    • dispatchTouchEvent

      函数原型

       public boolean dispatchTouchEvent(MotionEvent ev) 

      主要的功能是负责事件的分发.
      返回值:
      true: 表示向下分发中断
      false: 表示继续向下分发

    • onInterceptTouchEvent

      函数原型

       public boolean onInterceptTouchEvent(MotionEvent event) 

      主要功能是负责事件的拦截
      返回值:
      true:拦截,事件交由自己(View/ViewGroup)的onTouchEvent(...)处理
      false:不拦截,事件继续向下分发.

    • onTouchEvent

      函数原型

       public boolean onTouchEvent(MotionEvent event) 

      主要功能是处理触摸事件
      返回值:
      true:表示消费了这个事件.
      false:表示没有消费该事件,返回到上级处理.如果一直得不到处理,最终反馈到Activity的onTouchEvent(...)

  2. 函数之间的逻辑关系

    • 以上三个函数的伪代码

      类似于递归调用的方式

       public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean consume = false;
            if (onInterceptTouchEvent(ev)) {
                consume = onTouchEvent(ev);
            } else {
                consume = child.dispatchTouchEvent(ev);
            }
            return consume;
        } 
    • 函数与监听接口

      在通常情况下,我们为Button等组件设置了onClickListener接口,有时也会设置onTouchListener接口,但在什么时候接口中的方法才会执行呢?如果设置了onTouchListener接口监听,会对View(ViewGroup)的onTouchEvent有一定的影响.如果设置了onTouchListener,她的onTouch的返回值会影响view中onTouchEvent的调用与否,onTouch返回值的含义与onTouchEvent一样,表示是否消费了该事件.onTouch会先于onTouchEvent执行.伪代码为

       //true表示消费掉
         if(!listener.onTouch(ev)){
             onTouchEvent(ev);
         } 

      对于onClickListener接口,他内部方法onCLick的调用是在onTouchEvent中(根据上面就知道如果在onTouchListener的onTouch中返回true,onclick就不会再执行了),其内部部分代码如下.

       //View#onTouchEvent(...)
         if (mPerformClick == null) {
            mPerformClick = new PerformClick();
         }
         if (!post(mPerformClick)) {
            performClick();
         }
      
         //点击事件的处理者 
         private final class PerformClick implements Runnable {
            @Override
            public void run() {
                performClick();
            }
        }
      
        //点击调用onClick函数
        public boolean performClick() {
            //ListenerInfo封装了各种监听
            final ListenerInfo li = mListenerInfo;
            if (...) {
                //调用部分
                li.mOnClickListener.onClick(this);
                result = true;
            }
            ...
            return result;
        } 

      根据上面的描述,知道调用顺序为onTouchListener#onTouch,返回值决定是否继续执行view的onTouchEvent,最后在onTouchEvent中执行onClickListener的onClick方法.

  3. 分发过程

    • Activity分发

      触摸事件最先到达Activity,所以首先会在Activity中分发

       //Activity#dispatchTouchEvent()
             public boolean dispatchTouchEvent(MotionEvent ev) {
                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                     onUserInteraction();
                 }
                 //分发到Window.
                 if (getWindow().superDispatchTouchEvent(ev)) {
                     //true表示不再向下分发
                     return true;
                 }
                 return onTouchEvent(ev);
             } 

      在getWindow()中返回mWindow,最终在函数attach(...)中发现

       mWindow = new PhoneWindow(this); 

      PhoneWindow不在SDK中,在在线源码(Android源码)网站上可以找到相关的代码

       public boolean superDispatchTouchEvent(MotionEvent event ) {
                 //DecorView extends FrameLayout 
                 //       DecorView#superDispatchTouchEvent(ev)
                 //       public boolean superDispatchTouchEvent(MotionEvent event) {
                 //               //来到了ViewGroup
                 //               return super.dispatchTouchEvent(event);
                 //       }
                 return mDecorView.superDispatchTouchEvent(event);
             } 

      由此就把事件分发到了ViewGroup,接下来就是在VieGroup中分发.

  • View分发

    函数dispatchTouchEvent(...)中的部分代码

     ...
           if (onFilterTouchEventForSecurity(event)) {
               //noinspection SimplifiableIfStatement
               ListenerInfo li = mListenerInfo;
               if (li != null && li.mOnTouchListener != null
                       && (mViewFlags & ENABLED_MASK) == ENABLED
                       && li.mOnTouchListener.onTouch(this, event)) {
                   result = true;
               }
               // result==true,函数onTouchEvent(...)就执行不到了,而影想result的主要就是
               //li.mOnTouchListener.onTouch(this, // event)的返回值,返回true,
               //表示事件被处理了,自然不需要在调用onTouchEvent(...)来重新处理
               // 前面说过onClick(...)是在onTouchEvent(...)中调用的.即优先级小于onTouch()
               if (!result && onTouchEvent(event)) {
                   result = true;
               }
           }
           ... 

    函数onTouchEvent(...)主要就是处理事件,前面已经说过onClick的执行过程了.这里就不说了.

  • ViewGroup分发

    函数dispatchTouchEvent(...)中的部分代码

     // Check for interception.
           final boolean intercepted;
           // 事件为ACTION_DOWN或者mFirstTouchTarget不为null
           //(即已经找到能够接收touch事件的目标组件)时if成立
           if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
               //判断disallowIntercept(禁止拦截)标志位
               //因为在其他地方可能调用了
               //requestDisallowInterceptTouchEvent(boolean disallowIntercept)
               //从而禁止执行是否需要拦截的判断
               //(有点拗口~其实看requestDisallowInterceptTouchEvent()方法名就可明白)
               final boolean disallowIntercept = 
                                  (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
               //补充:根据下面的代码可以发现, disallowIntercept 的值等于函数
               //requestDisallowInterceptTouchEvent的参数.                 
               if (!disallowIntercept) {
                   intercepted = onInterceptTouchEvent(ev);
                   ev.setAction(action); // restore action in case it was changed
               } else {
                   intercepted = false;
               }
           } else {
               // There are no touch targets and this action is not an initial down
               // so this view group continues to intercept touches.
               intercepted = true;
           } 

    注意上文代码中的注释部分,这里看一下部分requesrDisallowInterceptTouchEvent(...)的部分源码

     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
                   //更具这里可以看出,当disallowIntercept=true时,
                   //(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 成立,
                   //这就意味着上面一段代码中的disallowIntercept=true;
                   if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
                       // We're already in this state, assume our ancestors are too
                       return;
                   }
                   ...
          } 

    由此可见VIewGroup只会在ACTION=ACTION_DOWN或者mFirstTouchTarget != null时才判断是否拦截事件,因为一个事件序列(DOWN->MOVE->...->UP)只能有一个View处理.但是mFirstTouchTarget != null表示什么呢?

    当事件被ViewGroup的子元素成功处理了(子View的onTouchEvent/onTouch返回了true??),mFirstTouchTarget被赋值指向子元素(即!=null)

    函数dispatchTouchEvent(...)的部分实现.

     final View[] children = mChildren;
       for (int i = childrenCount - 1; i >= 0; i--) {
           final int childIndex = customOrder
                   ? getChildDrawingOrder(childrenCount, i) : i;
           final View child = (preorderedList == null)
                   ? children[childIndex] : preorderedList.get(childIndex);
           // If there is a view that has accessibility focus we want it
           // to get the event first and if not handled we will perform a
           // normal dispatch. We may do a double iteration but this is
           // safer given the timeframe.
           if (childWithAccessibilityFocus != null) {
               if (childWithAccessibilityFocus != child) {
                   continue;
               }
               childWithAccessibilityFocus = null;
               i = childrenCount - 1;
           }
    
           if (!canViewReceivePointerEvents(child)
                   || !isTransformedTouchPointInView(x, y, child, null)) {
               ev.setTargetAccessibilityFocus(false);
               continue;
           }
           newTouchTarget = getTouchTarget(child);
           if (newTouchTarget != null) {
               // Child is already receiving touch within its bounds.
               // Give it the new pointer in addition to the ones it is handling.
               // 找到接收Touch事件的子View!!!!!!!即为newTouchTarget.
               newTouchTarget.pointerIdBits |= idBitsToAssign;
               break;
           }
    
           resetCancelNextUpFlag(child);
           //注意这个方法,再后面再看看..根据源码,
           //可以知道它返回的是子View(child)的dispatchTouchEvent(...)
           //当child==null,返回super.dispatchTouchEvent(...),
           //即View的dispatchTouchEvent(...)
           if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
               // Child wants to receive touch within its bounds.
               mLastTouchDownTime = ev.getDownTime();
               if (preorderedList != null) {
                   // childIndex points into presorted list, find original index
                   for (int j = 0; j < childrenCount; j++) {
                       if (children[childIndex] == mChildren[j]) {
                           mLastTouchDownIndex = j;
                           break;
                       }
                   }
               } else {
                   mLastTouchDownIndex = childIndex;
               }
               mLastTouchDownX = ev.getX();
               mLastTouchDownY = ev.getY();
               //找到了事件的处理者,终止循环
               newTouchTarget = addTouchTarget(child, idBitsToAssign);
               alreadyDispatchedToNewTouchTarget = true;
               break;
           }
    
           // The accessibility focus didn't handle the event, so clear
           // the flag and do a normal dispatch to all children.
           ev.setTargetAccessibilityFocus(false);
       } 

同样是dispatchTouchEvent(...)的部分代码

 // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                //这里说明没有子View处理该事件,只得有View的dispatchTouchEvent(...)来处理.
                //关于该函数的部分源码在后面介绍.
                handled = dispatchTransformedTouchEvent(ev, canceled, null/*child*/,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
             ...   
            }

 函数addTouchTarget(...)的具体实现.

            private TouchTarget addTouchTarget(View child, int pointerIdBits) {
                TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
                target.next = mFirstTouchTarget;
                mFirstTouchTarget = target;
                return target;
            }

  函数dispatchTransformedTouchEvent(...)的部分实现.

        ....
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        ....
        return handled. 

3.4 View的滑动冲突

  • 常见的滑动冲突的场景:

    1. 外部滑动方向和内部滑动方向不一致,例如viewpager中包含listview;
    2. 外部滑动方向和内部滑动方向一致,例如viewpager的单页中存在可以滑动的bannerview;
    3. 上面两种情况的嵌套,例如viewpager的单个页面中包含了bannerview和listview。
  • 滑动冲突处理规则

    可以根据滑动距离和水平方向形成的夹角;或者根绝水平和竖直方向滑动的距离差;或者两个方向上的速度差等

  • 解决方式

    1. 外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。该方法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,其他均不需要做修改。伪代码如下:

       public boolean onInterceptTouchEvent(MotionEvent event) {
           boolean intercepted = false;
           int x = (int) event.getX();
           int y = (int) event.getY();
      
           switch (event.getAction()) {
           case MotionEvent.ACTION_DOWN: {
               intercepted = false;
               break;
           }
           case MotionEvent.ACTION_MOVE: {
               int deltaX = x - mLastXIntercept;
               int deltaY = y - mLastYIntercept;
               if (父容器需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
                   intercepted = true;
               } else {
                   intercepted = false;
               }
               break;
           }
           case MotionEvent.ACTION_UP: {
               intercepted = false;
               break;
           }
           default:
               break;
           }
      
           mLastXIntercept = x;
           mLastYIntercept = y;
      
           return intercepted;
       } 
    2. 内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器来处理。这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。

       public boolean dispatchTouchEvent(MotionEvent event) {
             int x = (int) event.getX();
             int y = (int) event.getY();
      
             switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 getParent().requestDisallowInterceptTouchEvent(true);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
                 int deltaX = x - mLastX;
                 int deltaY = y - mLastY;
                 if (当前view需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
                     getParent().requestDisallowInterceptTouchEvent(false);
                 }
                 break;
             }
             case MotionEvent.ACTION_UP: {
                 break;
             }
             default:
                 break;
             }
             mLastX = x;
             mLastY = y;
             return super.dispatchTouchEvent(event);
         } 

      父View的onInterceptTouchEvent(...)伪代码

       public boolean  onInterceptTouchEvent(MotionEvent ev){
             if(ev.getAction() == MotionEvent.ACTION_DOWN){
                 retuen false;
             }else{
                 retuen true;
             }
         } 

      内部拦截法过程说明,父类在ACTION_DOWN时不拦截,子类在ACTION_DOWN时拦截,这时mFirstTouchTarget!=null, disallowIntercept = true,这意味着父类的onInterceptTouchEvent(...)不会再被执行,并且一个事件序列只有一个View来处理,则所有的后续ACTION_MOVE都会传到子View,当在子View中判断到某个事件应该由父View处理,只需重置disallowIntercept=false即可,即调用函数requestDisallowInterceptTouchEvent(false),这时事件就到父View的onTouchEvent(...)处理的(因为onInterceptionTouchEvent在非ACTION_DOWN时都返回true).如果父类没有在设置requestDisallowInterceptTouchEvent(true)的话,这个事件就会一直都在父View中做处理了.(注:为个人理解,若有不对,望其指出)

收藏
评论区

相关推荐

android view 常用的6种 View 的滑动方法
View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。   View 的滑动是Android
Android-自定义view
要自定义view,都知道有3个方法需要重写:onMeasure、onLayout、onDraw。而且这三个方法的执行是按顺序的。 生命周期image.png 实际开发中,比较多的自定义都是具体实现一个view的子类,实现viewgroup的子类比较少,两者基本相似,区别就是view需要实现onMeasure、onLayout、onDraw三个方法,而vie
Android输入系统(一)输入事件传递流程和InputManagerService的诞生
Android框架层 Android输入系统 Android框架层本文首发于微信公众号「刘望舒」 前言很多同学可能会认为输入系统是不是和View的事件分发有些关联,确实是有些关联,只不过View事件分发只能算是输入系统事件传递的一部分。这个系列讲的输入系统主要是我们不常接触的,但还是需要去了解的那部分。 1. 输入事件传递流程的组成部分输入系统是外界与And
Android窗口管理框架:Android应用视图的管理者Window
Android窗口管理框架:Android应用视图的管理者Window文章目录 一 窗口类型 二 窗口参数 三 窗口模式 四 窗口回调 五 窗口实现从这篇文章开始,我们来分析和Window以及WindowManager相关的内容,Abstract base class for a toplevel window look and behavior polic
java编程中使用二进制进行权限或状态控制
直接看代码以及注释吧。 @Test public void main() { // PC WEB端 int pc = 1 << 0;// ...0001=1 // Android端 int android = 1 <<
Android自定义ViewGroup:onMeasure与onLayout(1)
**Android自定义ViewGroup:onMeasure与onLayout(1)** Android自定义一个ViewGroup,需要重写ViewGrouo里面的两个最重要的回调函数onMeasure()与onLayout()。如果开发者自己摆脱Android为我们做好的几套布局(如常见的线1性布局、相对布局、帧布局等等),往底层实现vi
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结 ============================ 2019年了搜集了很多面试题,希望能对大家有所帮助 **1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结 ============================ 2019年了搜集了很多面试题,希望能对大家有所帮助 **1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
Android BLE 总结-源码篇(BluetoothLeAdvertiser)
在做Android BLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiser的startAdvertising方法,如下所示: **\[java\]** [view plain](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fblog.csdn.net%2Fan
Android WebView 的三种使用方式
关于原生开发好,还是混合模式开发好,还是 套壳的方式好,在这里不是重点,没有最好的,只有相对适合的。 重点是 那种方式 以最低的资源代价 适合你的业务场景,适合你的团队,根据实际情况来做技术选型。 1,使用h5替代android的view xml 前端将写好的h5 页面放在android 工程的asset目录, 打包的时候会将h5页面一起打在apk里面,
Android webview获取html的内容
  Android用文本view加载HTML时,可以首先获取到html的内容,然后对html做自己想要的效果调整     [@Override](https://my.oschina.net/u/1162528)     protected void onCreate(Bundle savedInstanceState) {         supe
Android中不规则形状View的布局实现
在Android中不管是View还是ViewGroup,都是方的! 方的! 方的! 而对于非方形的,Android官方并没有给出非常好的解决方案.有的无非就是自定义View了. 然而自定义View非常麻烦,需要重写很多方法,而且稍微不注意可能就会丧失一些特性或者造成一些Bug. 而且即便是自定义View,其实那个自定义View还是方的!!!,自定义V
Android学习笔记(二) 布局方式的介绍
Android应用的开发的一项内容就是用户界面开发了。Android 提供了大量功能丰富的UI组件。Android的界面是由布局和组件协同完成的。 ![](http://static.oschina.net/uploads/space/2014/0304/213204_usD8_865771.gif)  Android所有UI组件都继承了View
Android添加横线和竖线分割界面
竖线 <View       android:layout\_width="1dip"      android:layout\_height="match\_parent"     android:background="#66CCFF"     android:layout\_gravity="center\_horizontal"
Android的Surface的创建
`ViewRootImpl`管理着整个view tree。 对于`ViewRootImpl.setView()`,我们可以简单的把它当做一个`UI渲染操作`的入口。 [http://androidxref.com/6.0.1\_r10/xref/frameworks/base/core/java/android/view/WindowManagerImpl