2018-09-09 关于用户登录的过程(用Fragment去实现)

之前已经说过如何进行登录界面的编写,一个好的app是需要从用户的角度去出发的,我们登录界面不仅能提供用户密码登录,还应该能支持用户短信登录甚至是短信修改密码,这样就需要我们用更多的精力去优化我们的代码.首先说一下本篇文章的重点
1.关于如何进行Fragment的跳转
2.如何进行Fragment之前的传参
3.如何使从当前Fragment回退到我们需要的界面
4.如何使用mobSdk进行短信验证.
5.对于用户输入信息的判断.
这样,我们一点一点来分析,首先,我们需要进行Fragment的跳转。这个问题很重要,因为我们的界面肯定是有按键需求的,当用户按下一个键后,会要跳转到我们需要的界面上,上一篇文章在最后提到可以用接口回调的方式去实现,其实这样太麻烦了,我们可以用事务处理的方式去实现对Fragment管理.

public class FragmentTranscationUtil {
    //获取FragmentManager,开启事务,添加碎片
    public static void replaceFragment(FragmentActivity activity, Fragment fragment) {
        FragmentManager manager = activity.getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.setCustomAnimations(R.anim.activity_right_in, 0, 0, R.anim.activity_right_out);
        transaction.replace(R.id.userloginFrame_layout, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    //获取FragmentManager,开启事务,添加碎片
    public static void replaceFragment(FragmentActivity activity, Fragment fragment, Bundle bundle) {
        FragmentManager manager = activity.getSupportFragmentManager();
        fragment.setArguments(bundle);
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.setCustomAnimations(R.anim.activity_right_in, 0, 0, R.anim.activity_right_out);
        transaction.replace(R.id.userloginFrame_layout, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

这是一个帮助类,参数很简单,首先,我们要获取当前所在Activity,这个不用多说吗,因为Fragment多是嵌套在Activity里面的,第二个参数就是我们需要跳转的目标Fragment
下面第二个构造函数的第三个参数Bundle则是用来进行传参的.这个我下面会详细讲解
首先,我们要获取到FragmentManger的实例manger,然后manager.beginTransaction();
去开启事务,将我们需要执行的逻辑放入到事务中,在调用transaction.commit();去执行
就行了,这里顺便向大家展示一下Fragment生命周期
Fragment的生命周期和activity生命周期很像,其生命周期方法如下所示。

  1. onAttach:绑定到activity
  2. onCreate:创建fragment
  3. onCreateView: 创建fragment的布局
  4. onActivityCreated: activity创建完成后
  5. onStart: 可见, 不可交互
  6. onResume: 可见, 可交互
  7. onPause: 部分可见, 不可交互
  8. onStop:不可见
  9. onDestroyView: 销毁fragment的view对象
  10. onDestroy: fragment销毁了
  11. onDetach: 从activity解绑了
    下面给出FragmentTransaction的全部方法(API 24)转载自https://www.jianshu.com/p/5761ee2d3ea1
  • add(Fragment fragment, String tag) // 调用add(int, Fragment, String),填入为0的containerViewId.
  • add(int containerViewId, Fragment fragment) // 调用add(int, Fragment, String),填入为null的tag.
  • add(int containerViewId, Fragment fragment, String tag) // 向Activity中添加一个Fragment.
  • addSharedElement(View sharedElement, String name) // 添加共享元素
  • addToBackStack(String name) // 将事务添加到回退栈
  • attach(Fragment fragment) // 重新关联Fragment(当Fragment被detach时)
  • commit() // 提交事务
  • commitAllowingStateLoss() // 类似commit(),但允许在Activity状态保存之后提交(即允许状态丢失)。
  • commitNow() // 同步提交事务
  • commitNowAllowingStateLoss() // 类似commitNow(),但允许在Activity状态保存之后提交(即允许状态丢失)。
  • detach(Fragment fragment) // 将fragment保存的界面从UI中移除
  • disallowAddToBackStack() // 不允许调用addToBackStack(String)操作
  • hide(Fragment fragment) // 隐藏已存在的Fragment
  • isAddToBackStackAllowed() // 是否允许添加到回退栈
  • isEmpty() // 事务是否未包含的任何操作
  • remove(Fragment fragment) // 移除一个已存在的Fragment
  • replace(int containerViewId, Fragment fragment) // 调用replace(int, Fragment, String)填入为null的tag.
  • replace(int containerViewId, Fragment fragment, String tag) // 替换已存在的Fragment
  • setBreadCrumbShortTitle(int res) // 为事务设置一个BreadCrumb短标题
  • setBreadCrumbShortTitle(CharSequence text) // 为事务设置一个BreadCrumb短标题,将会被FragmentBreadCrumbs使用
  • setBreadCrumbTitle(int res) // 为事务设置一个BreadCrumb全标题,将会被FragmentBreadCrumbs使用
  • setBreadCrumbTitle(CharSequence text) // 为事务设置一个BreadCrumb全标题
  • setCustomAnimations(int enter, int exit, int popEnter, int popExit) // 自定义事务进入/退出以及入栈/出栈的动画效果
  • setCustomAnimations(int enter, int exit) // 自定义事务进入/退出的动画效果
  • setTransition(int transit) // 为事务设置一个标准动画
  • setTransitionStyle(int styleRes) // 为事务标准动画设置自定义样式
  • show(Fragment fragment) // 显示一个被隐藏的Fragment

这样我们就解决了Fragment跳转问题,同时Fragment回退问题也给出了答案,那就是利用addToBackStack(String name)
我们知道Activity有任务栈,用户通过startActivity将Activity加入栈,点击返回按钮将Activity出栈。Fragment也有类似的栈,称为回退栈(Back Stack),回退栈是由FragmentManager管理的。

image

默认情况下,Fragment事务是不会加入回退栈的,如果想将Fragment加入回退栈并实现事物回滚,首先需要在commit()方法之前调用事务的以下方法将其添加到回退栈中:

  • addToBackStack(String name)
    这个方法在我们跳转Fragment时候将事务添加至回退栈
    Fragment的回退非常简单,然而这里又会出现一个新的问题,就是在修改后的案例每次只能回退到上一步操作,而并不能一次性回退到我们想要的位置,这样才更满足实际开发需要。这就需要我们来多了解事物回滚的相关原理,其实在Fragment回退时,默认调用FragmentManager的popBackStack()方法将最上层的操作弹出回退栈。当栈中有多层时,我们可以根据id或TAG标识来指定弹出到的操作所在层。
  • popBackStack(int id, int flags):其中id表示提交变更时commit()的返回值。
  • popBackStack(String name, int flags):其中name是addToBackStack(String tag)中的tag值。
    在上面2个方法里面,都用到了flags,其实flags有两个取值:0或FragmentManager.POP_BACK_STACK_INCLUSIVE。当取值0时,表示除了参数指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;当取值POP_BACK_STACK_INCLUSIVE时,表示连着参数指定的这一层一起退出栈。
    这样Fragment的跳转和回退就说到这里,接下来是传参问题,这个问题也很简单,因为Bundle的底层代码就是HasMap<key,value>组合,首先实例化一个bundle,然后用<key,value>储存想要保存的数据,然后调用
  • setArguments(bundle);//添加将要传递的bundle
    在目标Fragment,使用getArguments();去获取到传入的值,比较简单,就不在赘述了。这样,1,2,3,问题我们都已经解决
    接下来是重点!如何使用mobSDK去实现短信验证.
    首先给大家一张图,便于理解短信验证流程
    TIM截图20180930115033.png

我个人使用的是Mob开发者平台(http://sms.mob.com)注册,获取到Appkey和appSecret,这个非常重要

配置Gradle
1、打开项目根目录的build.gradle,在buildscrip–>dependencies 模块下面添加 classpath ‘com.mob.sdk:MobSDK:+’,如下所示;

buildscript {
    repositories {
        jcenter()
    }
 
    dependencies {
        ...
        classpath 'com.mob.sdk:MobSDK:+'
 
    }
}

2、在使用SMSSDK模块的build.gradle中,添加MobSDK插件和扩展,如:

// 添加插件
apply plugin: 'com.mob.sdk'

// 在MobSDK的扩展中注册SMSSDK的相关信息
MobSDK {
    appKey "d580ad5*****"//填写自己获取的appKey
    appSecret "7fcae59a******7e2759e9e397c82bdd"//填写自己获取的appSecret

    SMSSDK {}
}

这里面的appkey和appSecret是自己注册而获取到的.替换一下就行了,
3、初始化MobSDK
如果您没有在AndroidManifest中设置appliaction的类名,MobSDK会将这个设置为com.mob.MobApplication,但如果您设置了,请在您自己的Application类中调用:

MobSDK.init(this);

以初始化MobSDK。
具体代码
这里有两种方式去实现,一个是可视化界面,一个是非UI实现,
因为我个人是是使用无UI的方式,所以会讲述无Ui实现的方法,这里给出可视化界面的文档(http://wiki.mob.com/sdk-sms-android-3-0-0/
首先介绍一下EventHandler这是一个异步操作,和Handler类似所有的业务逻辑将会在这里处理,它是和registerEventHandler一起使用的,registerEventHandler是一个事件接收器,他是专门负责接收所有被触发的事件,而registerEventHandler本身可以注册多个,所有接收器也会在事件被触发时候接收消息.一般是配套使用,否则容易产生内存泄露
下面给出具体代码

 //请求短信验证码
    private void SendCode(String country, String number) {
        EventHandler eh = new EventHandler() {
            @Override
            public void afterEvent(int i, int i1, Object o) {
                if (i == SMSSDK.EVENT_GET_VERIFICATION_CODE) {//获取短信验证码事件
                    //获取验证码成功
                    if (i1 == SMSSDK.RESULT_COMPLETE) {
                        listener.getCodeSuccess();
                    } else if (i1 == SMSSDK.RESULT_ERROR) {
                        listener.getCodeFailure();
                    }
                }
            }
        };
        SMSSDK.registerEventHandler(eh);
        SMSSDK.getVerificationCode(country, number);
    }

可以看到这是我自定义的一个方法,首先获取国家,中国是86开头,没啥好说的,然后是你将要发送短信验证的用户手机号码,这也没啥好说的,至于listener是我自定义的接口,会统一处理事务逻辑.

 //提交验证码
    private void SubmitCode(String country, final String number, String code) {
        EventHandler eh = new EventHandler() {
            @Override
            public void afterEvent(int i, int i1, Object o) {
                if (i == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {//验证码提交事件
                    if (i1 == SMSSDK.RESULT_COMPLETE) {
                        //回调成功
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                        listener.submitCodeSuccesss(number, dateFormat.format(new Date()));
                    } else if (i1 == SMSSDK.RESULT_ERROR) {//提交验证码失败
                        listener.submitCodeFailure();
                    }
                }
            }
        };
        SMSSDK.registerEventHandler(eh);
        SMSSDK.submitVerificationCode(country, number, code);
    }

提交验证码多出一个你接收短信验证的信息code,其他都差不多的。
这里给出mob业务逻辑,可以最大限度自定义出符合你自己要求的业务逻辑。

Handler mhandle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int event = msg.arg1;
            int result = msg.arg2;
            Object data = msg.obj;

            if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {//验证码提交事件
                if (result == SMSSDK.RESULT_COMPLETE) {
                    //回调成功
                    Toast.makeText(context, "提交验证码成功"+result, Toast.LENGTH_LONG).show();
                } else if (result == SMSSDK.RESULT_ERROR) {
                    Toast.makeText(context, "提交验证码失败"+data, Toast.LENGTH_LONG).show();
                }

            } else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE) {//获取短信验证码事件

                //获取验证码成功
                if (result == SMSSDK.RESULT_COMPLETE) {
                    Toast.makeText(context, "获取短信验证码成功", Toast.LENGTH_LONG).show();
                    boolean mobcheck = (Boolean) data;
                    if (mobcheck) {
                        //通过智能验证
                        Toast.makeText(context, "mob云验证", Toast.LENGTH_LONG).show();
                    } else {
                        //依然走短信验证
                        Toast.makeText(context, "短信验证", Toast.LENGTH_LONG).show();
                    }
                } else if (result == SMSSDK.RESULT_ERROR) {
                    Toast.makeText(context, "获取短信验证码失败"+data, Toast.LENGTH_LONG).show();
                }
            } else if (event == SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES) {
                SMSSDK.getSupportedCountries();
            } else {

                try {
                    ((Throwable) data).printStackTrace();
                    Throwable throwable = (Throwable) data;
                    JSONObject jsonObject = new JSONObject(throwable.getMessage());
                    String des = jsonObject.optString("detail");
                    int status = 0;
                    status = jsonObject.optInt("status");
                    if (TextUtils.isEmpty(des)) {

                    }
                } catch (Exception e) {
                    SMSLog.getInstance().w(e);
                }
            }
        }
    };

Ps:SMSSDK已经做了混淆处理,再次混淆会导致不可预期的错误,请在您的混淆脚本中添加如下的配置,跳过对SMSSDK的混淆操作:

-keep class com.mob.**{*;}
-keep class cn.smssdk.**{*;}
-dontwarn com.mob.**

这个时候,大家可能就有疑问了,没有UI界面我如何去输入收到的短信验证码去验证,很简单,自己写个UI就行了啊,一般现在流行的界面可能就是如下图所示了,引用一下图片https://www.jianshu.com/p/91b0b8038dd5

1879314-56d1f0d5b902bfee.png

这位大牛的效果还是不错的,我说下自己的思路,简单的就是TextView+Edittext去组合实现的,下面,附上xml资源,大家可以详细了解一下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorWhite">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_code1"
            style="@style/codeTextView" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_code2"
            style="@style/codeTextView" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_code3"
            style="@style/codeTextView" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_code4"
            style="@style/codeTextView" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_code5"
            style="@style/codeTextView" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_code6"
            style="@style/codeTextView" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal">

        <View
            android:id="@+id/view1"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <View
            android:id="@+id/view2"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <View
            android:id="@+id/view3"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <View
            android:id="@+id/view4"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <View
            android:id="@+id/view5"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <View
            android:id="@+id/view6"
            android:layout_width="40dp"
            android:layout_height="2dp"
            android:layout_gravity="bottom"
            android:background="@color/colorGray707061" />

    </LinearLayout>

    <lf.com.android.blackfishdemo.view.CodeEditTextView
        android:id="@+id/et_code_text"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#00000000"
        android:inputType="number"
        android:longClickable="false"
        android:maxLength="6"
        android:textColor="#00000000" />

</RelativeLayout>

乍一看貌似很复杂,其实就是绘制了可以输入6位数的Textview和对应的下标Item,然后上面再被我自定义的EditText所覆盖


/**
 * 验证码控件,去掉传统EditText双击选中EditText的内容
 * 和去掉光标位置会随点击改变
 */
public class CodeEditTextView extends AppCompatEditText {
    private long lastTime = 0;

    public CodeEditTextView(Context context) {
        super(context);
    }

    public CodeEditTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CodeEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSelectionChanged(int selStart, int selEnd) {
        super.onSelectionChanged(selStart, selEnd);
        this.setSelection(this.getText().length());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://判断点击事件,防止用户多次点击
                long currenTime = System.currentTimeMillis();
                if (currenTime - lastTime < 500) {
                    lastTime = currenTime;
                    return true;
                } else {
                    lastTime = currenTime;
                }
                break;


        }
        return super.onTouchEvent(event);
    }
}

自定义的Edittext首先会通过long currenTime = System.currentTimeMillis();获取到当前时间点,判断两个点击时间间隔,从而屏蔽双击事件,长按会走 onSelectionChanged这个方法,所以,设置光标始终在文本后面,也就屏蔽了长按事件
然后通过对Edittext事件进行TextWatcher监听(后面会详细讲解),依次将输入内容分写到Textview上,就完成了上面的效果.至此,如何使用短信验证就告一段落.
好了,同通过短信验证就意味着,用户登录过程已经接近尾声甚至已经结束了,但是,在这里,我还是想一起说了吧,因为短信验证不仅仅用于登录,也用于修改密码,而且,用户输入的内容我们要去判断,不可能用户随便输入一个文本我们就要对其进行一次短信验证吧,毕竟,喜欢找Bug的用户也不少,所以,我们要对用户输入的内容进行判断。这就是我么要说的5.对于用户输入信息的判断.在说这个之前,我先向大家介绍一下,什么是正则表达式
正则表达式是一种查找以及字符串替换操作。正则表达式在文本编辑器中广泛使用,比如正则表达式被用于:

  • 检查文本中是否含有指定的特征词
  • 找出文中匹配特征词的位置
  • 从文本中提取信息,比如:字符串的子串修改文本与文本编辑器相似,几乎所有的高级编程语言都支持正则表达式。在这样的语境下,“文本”也就是一个字符串,可以执行的操作都是类似的。一些编程语言(比如Perl,JavaScript)会检查正则表达式的语法。
    正则表达式的语法是是一种轻量级、简洁、适用于特定领域的编程语言。
    关于详解,建议大家去看这篇文章https://www.jianshu.com/p/67af3eeb6798
    这里给大家常用正则表达式的链接https://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html
    所以,当我们使用正则表达式时候,可利用事件监听
 private void setEditTextLitener() {
        mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
 
            }
        });
    }

我们可以通过监听afterTextChanged获取到输入内容,从而进行判断,然后去编写我们所需要的逻辑,至此,用户登录过程就完美结束了,欢迎大家留言,提出问题.谢谢大家的阅读。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,078评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,714评论 2 59
  • Fragment概述 Fragment是Activity中用户界面的一个行为或者说是一部分。主要是支持大屏幕上动态...
    wangling90阅读 11,536评论 5 75
  • 导语: Fragment作为Android最基本,最重要的基础概念之一,在开发中经常会和他打交道。本文从为什么出现...
    JYGod丶阅读 147,429评论 7 166
  • 我依然记得你那天的模样 你白色的裙裾随着 紫色的牵牛花在风中摇曳 夕阳照在你凌乱的秀发 你把忧伤写在脸上 我读成万...
    像风一样自由2017阅读 339评论 0 1