Android View体系(十一)自定义ViewGroup

相关文章
Android View体系(一)视图坐标系
Android View体系(二)实现View滑动的六种方法
Android View体系(三)属性动画
Android View体系(四)从源码解析Scroller
Android View体系(五)从源码解析View的事件分发机制
Android View体系(六)从源码解析Activity的构成
Android View体系(七)从源码解析View的measure流程
Android View体系(八)从源码解析View的layout和draw流程
Android View体系(九)自定义View
Android View体系(十)自定义组合控件

前言

此前讲了很多,终于可以讲到这一节了,本文的例子是一个自定义的ViewGroup,左右滑动切换不同的页面,类似一个特别简化的ViewPager,这篇文章会涉及到这个系列的很多文章的内容比如View的measure、layout和draw流程,view的滑动等等,所以对View体系不大了解的同学看这篇文章前可以先从头阅读本系列的其他文章,再来看这篇文章效果会更好些。需要注意的是我们知道要实现一个自定义的ViewGroup是很复杂的,这个看看LineraLayout等源码我们就会知道,这里我们只需要把主要的功能实现就好了。

1.继承ViewGroup

要实现自定义的ViewGroup,首先要继承ViewGroup并调用父类构造方法,实现抽象方法等。

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这个抽象方法是必须要实现的,我们暂且什么都不做。

2.对wrap_content属性进行处理

Android View体系(九)自定义View这篇文章中我们同样对wrap_content属性进行了处理不明白的可以查看这篇文章或者直接查看Android View体系(七)从源码解析View的measure流程来了解具体的原因,这里就不赘述了。

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup {
    //...省略此前的构造代码
    @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);
        //如果没有子元素,就设置宽高都为0(简化处理)
        if (getChildCount() == 0) { 
            setMeasuredDimension(0, 0);
        }
        //宽和高都是AT_MOST,则设置宽度所有子元素的宽度的和;高度设置为第一个元素的高度;
        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,则高度为第一个子元素的高度
        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) {
    }
}

这里如果没有子元素时采用了简化的写法直接将宽和高直接设置为0,正常的话我们应该根据LayoutParams中的宽和高来做相应的处理,另外我们在测量时没有考虑它的padding和子元素的margin。

3.实现onLayout

接下来我们实现onLayout,来布局子元素,因为每一种布局方式子View的布局都是不同的,所以这个是ViewGroup唯一一个抽象方法,需要我们自己去实现:

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 {
    //... 省略构造方法代码和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,则调用子元素的layout方法将其放置到合适的位置上,相当于默认第一个子元素占满了屏幕,后面的子元素就是在第一个屏幕后面紧挨着和屏幕一样大小的后续元素,所以left是一直累加的,top保持0,bottom保持第一个元素的高度,right就是left+元素的宽度,同样这里没有处理自身的pading以及子元素的margin。

4.处理滑动冲突

这个自定义ViewGroup是水平滑动,如果里面是ListView,则ListView是垂直滑动,如果我们检测到的滑动方向是水平的话,就让父View拦截用来进行View的滑动切换 :

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;
    //... 省略了构造函数的代码
  
    @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; 
                //用户想水平滑动的,所以拦截
                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.弹性滑动到其他页面

这里就会进入onTouchEvent事件,然后我们需要进行滑动切换页面,这里需要用到Scroller,具体请查看Android View体系(二)实现View滑动的六种方法这篇文章,而Scroller滑动的原理请查看 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 {
    //... 省略构造函数,init方法,onInterceptTouchEvent
    int lastInterceptX;
    int lastInterceptY;
    int lastX;
    int lastY;
    int currentIndex = 0; //当前子元素
    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; //跟随手指滑动
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP: 
             //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth;
                //滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                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();
        }
    }
    //弹性滑动到指定位置
    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.快速滑动到其他页面

我们不只滑动超过一半才切换到上/下一个页面,如果滑动速度很快的话,我们也可以判定为用户想要滑动到其他页面,这样的体验也是好的。 这部分也是在onTouchEvent中的ACTION_UP部分:
这里又需要用到VelocityTracker,它用来测试滑动速度的。使用方法也很简单,首先在构造函数中进行初始化,也就是前面的init方法中增加一条语句:

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

接着改写onTouchEvent部分:


@Override
    public boolean onTouchEvent(MotionEvent event) {
...
  case MotionEvent.ACTION_UP:
              //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth; 
                //必须滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                else {
                //调用该方法计算1000ms内滑动的平均速度   
                 tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity(); //获取到水平方向上的速度
                    //如果速度的绝对值大于50的话,就认为是快速滑动,就执行切换页面
                    if (Math.abs(xV) > 50) { 
                    //大于0切换上一个页面
                        if (xV > 0) { 
                            currentIndex--;
                    //小于0切换到下一个页面
                        } else { 
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                //重置速度计算器
                tracker.clear();
                break;
             }

7.再次触摸屏幕阻止页面继续滑动

当我们快速向左滑动切换到下一个页面的情况,在手指释放以后,页面会弹性滑动到下一个页面,可能需要一秒才完成滑动,这个时间内,我们再次触摸屏幕,希望能拦截这次滑动,然后再次去操作页面。
要实现在弹性滑动过程中再次触摸拦截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判断,如果在ACTION_DOWN的时候,scroller还没有完成,说明上一次的滑动还正在进行中,则直接终端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;
               
               //如果动画还没有执行完成,则打断
                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;
        }
        //因为DOWN返回true,所以onTouchEvent中无法获取DOWN事件,所以这里要负责设置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }
...


8.应用HorizontalView

首先我们在主布局中引用HorizontalView,它作为父容器,里面有两个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>

接着在代码中为ListView填加数据:

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);
    }
}

运行程序查看效果(录制有些问题listview的分割线显示不出来):

这里写图片描述

最后贴上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; //当前子元素
    private int childWidth = 0;
    private Scroller scroller;
    private VelocityTracker tracker;    //增加速度检测,如果速度比较快的话,就算没有滑动超过一半的屏幕也可以
    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;
                //如果动画还没有执行完成,则打断
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                //水平方向距离长  MOVE中返回true一次,后续的MOVE和UP都不会收到此请求
                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;
        }
        //因为DOWN返回true,所以onTouchEvent中无法获取DOWN事件,所以这里要负责设置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:
                //跟随手指滑动
                int deltaX = x - lastX;
                scrollBy(-deltaX, 0);
                break;
            //释放手指以后开始自动滑动到目标位置
            case MotionEvent.ACTION_UP:
                //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth;

                //必须滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                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);
        //测量所有子元素
        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源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 接上一篇:Android艺术开发探索第三章————View的事件体系(上) 3.4 View 的事件分发机制 本节...
    kongjn阅读 1,049评论 1 0
  • 能够遇见人智医学,真是机缘巧合,刚陪着孩子去逛幼儿园的时候,发现华德福幼儿园真是一个很落地的教育,孩子得到了充分的...
    薛宁阅读 1,113评论 0 1
  • 白瓯城里的老人到大水潮的月半,只要看到瓯江水位高的时候,总会想起70年前的那个中秋之夜。 老人们说:那天的...
    楠溪陈酿阅读 358评论 0 1
  • 生活 无奈的事情很多,遗憾的事情很多 不喜欢的事情很多,讨厌的事情很多 不顺心的事也不少,但不得不一一面对 奈何?...
    辰渔阅读 349评论 3 4