Android View體系(十一)自定義ViewGroup

相關(guān)文章
Android View體系(一)視圖坐標(biāo)系
Android View體系(二)實(shí)現(xiàn)View滑動(dòng)的六種方法
Android View體系(三)屬性動(dòng)畫
Android View體系(四)從源碼解析Scroller
Android View體系(五)從源碼解析View的事件分發(fā)機(jī)制
Android View體系(六)從源碼解析Activity的構(gòu)成
Android View體系(七)從源碼解析View的measure流程
Android View體系(八)從源碼解析View的layout和draw流程
Android View體系(九)自定義View
Android View體系(十)自定義組合控件

前言

此前講了很多,終于可以講到這一節(jié)了,本文的例子是一個(gè)自定義的ViewGroup,左右滑動(dòng)切換不同的頁(yè)面,類似一個(gè)特別簡(jiǎn)化的ViewPager,這篇文章會(huì)涉及到這個(gè)系列的很多文章的內(nèi)容比如View的measure、layout和draw流程,view的滑動(dòng)等等,所以對(duì)View體系不大了解的同學(xué)看這篇文章前可以先從頭閱讀本系列的其他文章,再來(lái)看這篇文章效果會(huì)更好些。需要注意的是我們知道要實(shí)現(xiàn)一個(gè)自定義的ViewGroup是很復(fù)雜的,這個(gè)看看LineraLayout等源碼我們就會(huì)知道,這里我們只需要把主要的功能實(shí)現(xiàn)就好了。

1.繼承ViewGroup

要實(shí)現(xiàn)自定義的ViewGroup,首先要繼承ViewGroup并調(diào)用父類構(gòu)造方法,實(shí)現(xiàn)抽象方法等。

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup{
    public HorizontalView(Context context) {
        super(context);
    }
    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }
}

這里我們定義了名字叫HorizontalView的類并繼承 ViewGroup,onLayout這個(gè)抽象方法是必須要實(shí)現(xiàn)的,我們暫且什么都不做。

2.對(duì)wrap_content屬性進(jìn)行處理

Android View體系(九)自定義View這篇文章中我們同樣對(duì)wrap_content屬性進(jìn)行了處理不明白的可以查看這篇文章或者直接查看Android View體系(七)從源碼解析View的measure流程來(lái)了解具體的原因,這里就不贅述了。

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup {
    //...省略此前的構(gòu)造代碼
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //如果沒(méi)有子元素,就設(shè)置寬高都為0(簡(jiǎn)化處理)
        if (getChildCount() == 0) { 
            setMeasuredDimension(0, 0);
        }
        //寬和高都是AT_MOST,則設(shè)置寬度所有子元素的寬度的和;高度設(shè)置為第一個(gè)元素的高度;
        else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth * getChildCount(), childHeight);
        }
        //如果寬度是wrap_content,則寬度為所有子元素的寬度的和
        else if (widthMode == MeasureSpec.AT_MOST) {
            int childWidth = getChildAt(0).getMeasuredWidth();
            setMeasuredDimension(childWidth * getChildCount(), heightSize);
        }
        //如果高度是wrap_content,則高度為第一個(gè)子元素的高度
        else if (heightMode == MeasureSpec.AT_MOST) {
            int childHeight = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight);
        }
      
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }
}

這里如果沒(méi)有子元素時(shí)采用了簡(jiǎn)化的寫法直接將寬和高直接設(shè)置為0,正常的話我們應(yīng)該根據(jù)LayoutParams中的寬和高來(lái)做相應(yīng)的處理,另外我們?cè)跍y(cè)量時(shí)沒(méi)有考慮它的padding和子元素的margin。

3.實(shí)現(xiàn)onLayout

接下來(lái)我們實(shí)現(xiàn)onLayout,來(lái)布局子元素,因?yàn)槊恳环N布局方式子View的布局都是不同的,所以這個(gè)是ViewGroup唯一一個(gè)抽象方法,需要我們自己去實(shí)現(xiàn):

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class HorizontalView extends ViewGroup {
    //... 省略構(gòu)造方法代碼和onMeasure的代碼
   
    @Override
       protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0;
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int width = child.getMeasuredWidth();
                childWidth = width; 
                child.layout(left, 0, left + width, child.getMeasuredHeight());
                left += width;
            }
        }
    }

遍歷所有的子元素,如果子元素不是GONE,則調(diào)用子元素的layout方法將其放置到合適的位置上,相當(dāng)于默認(rèn)第一個(gè)子元素占滿了屏幕,后面的子元素就是在第一個(gè)屏幕后面緊挨著和屏幕一樣大小的后續(xù)元素,所以left是一直累加的,top保持0,bottom保持第一個(gè)元素的高度,right就是left+元素的寬度,同樣這里沒(méi)有處理自身的pading以及子元素的margin。

4.處理滑動(dòng)沖突

這個(gè)自定義ViewGroup是水平滑動(dòng),如果里面是ListView,則ListView是垂直滑動(dòng),如果我們檢測(cè)到的滑動(dòng)方向是水平的話,就讓父View攔截用來(lái)進(jìn)行View的滑動(dòng)切換 :

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class HorizontalView extends ViewGroup {
    private int lastInterceptX;
    private int lastInterceptY;
    private int lastX;
    private int lastY;
    //... 省略了構(gòu)造函數(shù)的代碼
  
    @Override
    public boolean onInterceptHoverEvent(MotionEvent event) {
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX; 
                int deltaY = y - lastInterceptY; 
                //用戶想水平滑動(dòng)的,所以攔截
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true; 
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        lastX = x;
        lastY = y;
        lastInterceptX = x; 
        lastInterceptY = y;
        return intercept;
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
    //... 省略了onMeasure和onLayout的代碼
}

5.彈性滑動(dòng)到其他頁(yè)面

這里就會(huì)進(jìn)入onTouchEvent事件,然后我們需要進(jìn)行滑動(dòng)切換頁(yè)面,這里需要用到Scroller,具體請(qǐng)查看Android View體系(二)實(shí)現(xiàn)View滑動(dòng)的六種方法這篇文章,而Scroller滑動(dòng)的原理請(qǐng)查看 Android View體系(四)從源碼解析Scroller這篇文章。

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class HorizontalView extends ViewGroup {
    //... 省略構(gòu)造函數(shù),init方法,onInterceptTouchEvent
    int lastInterceptX;
    int lastInterceptY;
    int lastX;
    int lastY;
    int currentIndex = 0; //當(dāng)前子元素
    int childWidth = 0; 
    private Scroller scroller;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastX; //跟隨手指滑動(dòng)
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP: 
             //相對(duì)于當(dāng)前View滑動(dòng)的距離,正為向左,負(fù)為向右
                int distance = getScrollX() - currentIndex * childWidth;
                //滑動(dòng)的距離要大于1/2個(gè)寬度,否則不會(huì)切換到其他頁(yè)面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                smoothScrollTo(currentIndex * childWidth, 0);
                break;
        }
        lastX = x;
        lastY = y;
        return super.onTouchEvent(event);
    }
    //...省略onMeasure方法
     @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    //彈性滑動(dòng)到指定位置
    public void smoothScrollTo(int destX, int destY) {
        scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000); 
        invalidate();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0; 
        View child;
        //遍歷布局子元素
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            int width = child.getMeasuredWidth();
            //賦值為子元素的寬度
            childWidth = width; 
            child.layout(left, 0, left + width, child.getMeasuredHeight());
            left += width;
        }
    }
}

6.快速滑動(dòng)到其他頁(yè)面

我們不只滑動(dòng)超過(guò)一半才切換到上/下一個(gè)頁(yè)面,如果滑動(dòng)速度很快的話,我們也可以判定為用戶想要滑動(dòng)到其他頁(yè)面,這樣的體驗(yàn)也是好的。 這部分也是在onTouchEvent中的ACTION_UP部分:
這里又需要用到VelocityTracker,它用來(lái)測(cè)試滑動(dòng)速度的。使用方法也很簡(jiǎn)單,首先在構(gòu)造函數(shù)中進(jìn)行初始化,也就是前面的init方法中增加一條語(yǔ)句:

 ...
  private VelocityTracker tracker;    
  ...
  public void init() {
        scroller = new Scroller(getContext());
        tracker=VelocityTracker.obtain();
    }
  ...

接著改寫onTouchEvent部分:


@Override
    public boolean onTouchEvent(MotionEvent event) {
...
  case MotionEvent.ACTION_UP:
              //相對(duì)于當(dāng)前View滑動(dòng)的距離,正為向左,負(fù)為向右
                int distance = getScrollX() - currentIndex * childWidth; 
                //必須滑動(dòng)的距離要大于1/2個(gè)寬度,否則不會(huì)切換到其他頁(yè)面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                else {
                //調(diào)用該方法計(jì)算1000ms內(nèi)滑動(dòng)的平均速度   
                 tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity(); //獲取到水平方向上的速度
                    //如果速度的絕對(duì)值大于50的話,就認(rèn)為是快速滑動(dòng),就執(zhí)行切換頁(yè)面
                    if (Math.abs(xV) > 50) { 
                    //大于0切換上一個(gè)頁(yè)面
                        if (xV > 0) { 
                            currentIndex--;
                    //小于0切換到下一個(gè)頁(yè)面
                        } else { 
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                //重置速度計(jì)算器
                tracker.clear();
                break;
             }

7.再次觸摸屏幕阻止頁(yè)面繼續(xù)滑動(dòng)

當(dāng)我們快速向左滑動(dòng)切換到下一個(gè)頁(yè)面的情況,在手指釋放以后,頁(yè)面會(huì)彈性滑動(dòng)到下一個(gè)頁(yè)面,可能需要一秒才完成滑動(dòng),這個(gè)時(shí)間內(nèi),我們?cè)俅斡|摸屏幕,希望能攔截這次滑動(dòng),然后再次去操作頁(yè)面。
要實(shí)現(xiàn)在彈性滑動(dòng)過(guò)程中再次觸摸攔截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判斷,如果在ACTION_DOWN的時(shí)候,scroller還沒(méi)有完成,說(shuō)明上一次的滑動(dòng)還正在進(jìn)行中,則直接終端scroller:

...
 @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
     
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: 
                intercept = false;
               
               //如果動(dòng)畫還沒(méi)有執(zhí)行完成,則打斷
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:          
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true;
                } else {
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        //因?yàn)镈OWN返回true,所以onTouchEvent中無(wú)法獲取DOWN事件,所以這里要負(fù)責(zé)設(shè)置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }
...


8.應(yīng)用HorizontalView

首先我們?cè)谥鞑季种幸肏orizontalView,它作為父容器,里面有兩個(gè)ListView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.liuwangshu.mooncustomviewgroup.MainActivity">
    <com.example.liuwangshu.mooncustomviewgroup.HorizontalView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
       <ListView
        android:id="@+id/lv_one"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
        <ListView
            android:id="@+id/lv_two"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>
    </com.example.liuwangshu.mooncustomviewgroup.HorizontalView>
</RelativeLayout>

接著在代碼中為L(zhǎng)istView填加數(shù)據(jù):

package com.example.liuwangshu.mooncustomviewgroup;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    private ListView lv_one;
    private ListView lv_two;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv_one=(ListView)this.findViewById(R.id.lv_one);
        lv_two=(ListView)this.findViewById(R.id.lv_two);
        String[] strs1 = {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"};
        ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs1);
        lv_one.setAdapter(adapter1);

        String[] strs2 = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"};
        ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs2);
        lv_two.setAdapter(adapter2);
    }
}

運(yùn)行程序查看效果(錄制有些問(wèn)題listview的分割線顯示不出來(lái)):

這里寫圖片描述

最后貼上HorizontalView的源碼:

package com.example.liuwangshu.mooncustomviewgroup;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class HorizontalView extends ViewGroup {
    private int lastX;
    private int lastY;
    private int currentIndex = 0; //當(dāng)前子元素
    private int childWidth = 0;
    private Scroller scroller;
    private VelocityTracker tracker;    //增加速度檢測(cè),如果速度比較快的話,就算沒(méi)有滑動(dòng)超過(guò)一半的屏幕也可以
    private int lastInterceptX=0;
    private int lastInterceptY=0;
    public HorizontalView(Context context) {
        super(context);
        init();
    }
    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init() {
        scroller = new Scroller(getContext());
        tracker = VelocityTracker.obtain();
    }

    //todo intercept的攔截邏輯
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                //如果動(dòng)畫還沒(méi)有執(zhí)行完成,則打斷
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                //水平方向距離長(zhǎng)  MOVE中返回true一次,后續(xù)的MOVE和UP都不會(huì)收到此請(qǐng)求
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) {
                    intercept = true;
                    Log.i("wangshu","intercept = true");
                } else {
                    intercept = false;
                    Log.i("wangshu","intercept = false");
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        //因?yàn)镈OWN返回true,所以onTouchEvent中無(wú)法獲取DOWN事件,所以這里要負(fù)責(zé)設(shè)置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        tracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //跟隨手指滑動(dòng)
                int deltaX = x - lastX;
                scrollBy(-deltaX, 0);
                break;
            //釋放手指以后開始自動(dòng)滑動(dòng)到目標(biāo)位置
            case MotionEvent.ACTION_UP:
                //相對(duì)于當(dāng)前View滑動(dòng)的距離,正為向左,負(fù)為向右
                int distance = getScrollX() - currentIndex * childWidth;

                //必須滑動(dòng)的距離要大于1/2個(gè)寬度,否則不會(huì)切換到其他頁(yè)面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                } else {
                    tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity();
                    if (Math.abs(xV) > 50) {
                        if (xV > 0) {
                            currentIndex--;
                        } else {
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                tracker.clear();
                break;
            default:
                break;
        }
        lastX = x;
        lastY = y;
        return true;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //測(cè)量所有子元素
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //處理wrap_content的情況
        if (getChildCount() == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth * getChildCount(), childHeight);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth * getChildCount(), heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            int childHeight = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight);
        }
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    public void smoothScrollTo(int destX, int destY) {
        scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000);
        invalidate();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0; //左邊的距離
        View child;
        //遍歷布局子元素
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int width = child.getMeasuredWidth();
                childWidth = width; //賦值給子元素寬度變量
                child.layout(left, 0, left + width, child.getMeasuredHeight());
                left += width;
            }
        }
    }
}

github源碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,901評(píng)論 25 709
  • 接上一篇:Android藝術(shù)開發(fā)探索第三章————View的事件體系(上) 3.4 View 的事件分發(fā)機(jī)制 本節(jié)...
    kongjn閱讀 1,232評(píng)論 1 0
  • 能夠遇見人智醫(yī)學(xué),真是機(jī)緣巧合,剛陪著孩子去逛幼兒園的時(shí)候,發(fā)現(xiàn)華德福幼兒園真是一個(gè)很落地的教育,孩子得到了充分的...
    薛寧閱讀 1,348評(píng)論 0 1
  • 白甌城里的老人到大水潮的月半,只要看到甌江水位高的時(shí)候,總會(huì)想起70年前的那個(gè)中秋之夜。 老人們說(shuō):那天的...
    楠溪陳釀閱讀 463評(píng)論 0 2
  • 生活 無(wú)奈的事情很多,遺憾的事情很多 不喜歡的事情很多,討厭的事情很多 不順心的事也不少,但不得不一一面對(duì) 奈何?...
    辰漁閱讀 412評(píng)論 3 4

友情鏈接更多精彩內(nèi)容