GitHub安卓(Android)热门开源资源在项目中的使用及项目总结

jamlee 9年前
   <p><img src="https://simg.open-open.com/show/98a76f4bc941c9f4537cd3651d9b4c14.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="475" height="413"></p>    <h2>层级的划分</h2>    <p>我们写一个稍微复杂的App的时候,不可能只有一个包,必定分门别类。而分类的标准,大部分应该是遵循这三类<strong>view(UI层)</strong>,<strong>bussiness(逻辑处理层)</strong>,还有<strong>data(数据层)</strong>。然后我们根据这个标准把他们放在不同的包里面。这样一来,结构,逻辑都很清晰。例如这个项目里面:</p>    <p><img src="https://simg.open-open.com/show/1158a6b3bbc31a91a1f187caca56ccfc.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="230" height="144"></p>    <table>     <thead>      <tr>       <th>包名</th>       <th>用途</th>      </tr>     </thead>     <tbody>      <tr>       <td>Base</td>       <td>包含一些基类</td>      </tr>      <tr>       <td>domain</td>       <td>存放一些javabean的类</td>      </tr>      <tr>       <td>Fragment</td>       <td>顾名思义存放fragment的类</td>      </tr>      <tr>       <td>Global</td>       <td>存放网络的全局类</td>      </tr>      <tr>       <td>Utils</td>       <td>工具类</td>      </tr>      <tr>       <td>View</td>       <td>改写后的布局类</td>      </tr>     </tbody>    </table>    <p>它们分工明确,内容一目了然。每个人的习惯都不尽相同,找到自己熟悉的命名方式,结构明了就好。</p>    <ul>     <li>命名方式<br> 关于命名的规范<br> 1.对于类名我们使用:UpperCamelCase风格编写</li>    </ul>    <p><img src="https://simg.open-open.com/show/0f0dee05cd5b7e1e179cb3832383d7c1.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="192" height="89"></p>    <p><br> 2.对于方法名我们使用:LowerCamelCase风格编写</p>    <p><img src="https://simg.open-open.com/show/c4f7b7ef7357f6a5e47dbb1c3835dcd0.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="346" height="50"></p>    <p>这两个都是驼峰命名,一个是大驼峰(每个单词的首字母大写),一个是小驼峰(除第一个单词以外的单词首字母大写)。这种方式命名美观,简洁。我纵观了一下整个项目代码,发现有些时候不是按照上面的规则,而是例如fragment_left_menu这样的下划线命名,所以其实并没有唯一的标准,只是可以的话尽量这样命名我们的代码。</p>    <ul>     <li>基类的创建<br> 顾名思义,基类,即为初始类。如果两个以上的类同时拥有相似的特征,我们就把它抽取出来单独形成一个类。就好比我们在一个类的两个不同的函数中,需要使用同一个参数,这时我们也会习惯性的把它变成全局的变量供两个函数使用。</li>    </ul>    <p><img src="https://simg.open-open.com/show/a8d01cc5e5b425223174517bb8a2ace6.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="789" height="411"></p>    <p>假如我们现在要写5个页面,显然界面各不相同,但是我们要为它们编写一个共同的基类。我们发现无论页面怎么变,它的标题栏是大同小异的。</p>    <p><img src="https://simg.open-open.com/show/9c5f99598fd8532003de48425536b905.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="249" height="401"></p>    <p>标题栏大家很熟悉,ImageButton和TextView,下面我们不知道是什么,就用一个帧布局Framelayout代替。以后如果要添加一个不同的布局,它有一个方法<em>addview()</em>便搞定了</p>    <p>上面的代码,只是一个模板,给大家提供思路,我们需要初始化界面,初始化数据,<em>mMainActivity</em>的作用除了在<strong>Inflate函数里</strong>充当上下文的参数(应该改为public类型),其实在对象的流动传递里也有很大的作用,后面再叙述。除此之外如果你有必要,还可以来个类的初始化<em>public BaseTab(){}</em>,又或是把view抽取出来当作公共参数,又或者你必须规定子类实现某个方法,某个函数,那就把整个基类写成抽象类。</p>    <h2>开源项目的使用</h2>    <p><img src="https://simg.open-open.com/show/d3c710a56c4547c5368af585fad9dcde.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="570" height="173"></p>    <p><img src="https://simg.open-open.com/show/3989c053a570fedb86468d11b8e46e43.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="1033" height="345"></p>    <p><br> 我们随便百度,都可以搜索到一些很热门的,很好用的开源项目。当然还是像上一篇说的,你可以直接在github下载下来,然后关联到你的项目,具体做法上一篇已经讲了,现在我就假设你已经导入好了。</p>    <ul>     <li>Sliding Menu<br> 因为使用过程中,有牵扯到<strong>Fragment</strong>,所以就一起讲了。<br> <em>sliding menu</em>顾名思义侧边栏,最直观的如图片所示(网络图):</li>    </ul>    <p><img src="https://simg.open-open.com/show/dd33fdb3066c15b18e3c5748fd244d05.gif" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="320" height="568"></p>    <pre>  <code class="language-java">public class MainActivity extends SlidingFragmentActivity {         public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          requestWindowFeature(Window.FEATURE_NO_TITLE);          /* 都声明为framelayout  */               setContentView(R.layout.mainactiviy);          //设置側邊栏          setBehindContentView(R.layout.left_menu);          //获取侧边栏对象          SlidingMenu slidingMenu=getSlidingMenu();          //设置全屏拖动          slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);          //设置屏幕的预留宽度          slidingMenu.setBehindOffset(400);          initFragment();      }</code></pre>    <p>现在结合代码说下最简单的用法,我们首先继承SlidingFragmentActivity,然后我们为主页面和侧边栏都设置一个<em>xml</em>的布局,这两个布局都只有一个 <em>framelayout</em> 方便我们之后换成其他的布局,我们的主页面和侧边栏的页面都用fragment实现,等下再说。<br> 接着我们要获取侧边栏对象,它有个方法<em>getSlidingMenu()</em>,用as的朋友,直接再quick fix一下(Alt+Enter),对象就出来了。我们为它设置参数:</p>    <table>     <thead>      <tr>       <th>方法</th>       <th>用途</th>      </tr>     </thead>     <tbody>      <tr>       <td>setBehindContentView</td>       <td>设置侧边栏的布局文件</td>      </tr>     </tbody>    </table>    <table>     <thead>      <tr>       <th>方法</th>       <th>用途</th>      </tr>     </thead>     <tbody>      <tr>       <td>setTouchModeAbove</td>       <td>设置拖动的位置 有全屏 不设置 左边 右边等</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/a20278f3f3eb412d797b303173d8a723.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="329" height="221"></p>    <table>     <thead>      <tr>       <th>方法</th>       <th>用途</th>      </tr>     </thead>     <tbody>      <tr>       <td>setBehindOffset</td>       <td>设置屏幕的预留宽度 其实也就是变向设置你拖出来的大小</td>      </tr>     </tbody>    </table>    <table>     <thead>      <tr>       <th>方法</th>       <th>用途</th>      </tr>     </thead>     <tbody>      <tr>       <td>toggle</td>       <td>隐藏侧边栏</td>      </tr>     </tbody>    </table>    <p>接下来我们要创建两个<em>Fragment</em>去填补上面的<em>framelayout</em>。</p>    <ul>     <li>Fragment<br> 记得以前学习Fragment的时候,一头雾水,分为什么静态和动态。但随着时间推移,慢慢的就清晰很多了。所以当你遇到一时理解不了的问题,放一放,过一阵子再回头看,会明白许多。<br> 我们现在只讲如何动态创建Fragment:<br> <strong>大致流程应该是这样</strong><br> 1.我们的Activity 的布局文件只有一个帧布局<br> 2.创建管理者<br> 3.开启事物<br> 4.替换<br> 5.提交事务</li>    </ul>    <p>具体代码如下:</p>    <pre>  <code class="language-java">private  void initFragment(){        //管理者      FragmentManager fm = getSupportFragmentManager();      //开启事物      FragmentTransaction transaction = fm.beginTransaction();      //替换      transaction.replace(R.id.fl_left_menu,new Fragment_left_menu(),FRAGMENT_LEFT_MENU);    transaction.replace(R.id.fl_content, new Fragment_content(), FRAGMENT_CONTENT);      //提交      transaction.commit();}</code></pre>    <blockquote>     <p>我们在替换这个函数里面,3个参数分别代表:replace(对应framelayout的id,要替换的类,Tag标签)</p>    </blockquote>    <p>如果你只是一个fragment类,写完替换进去就搞定了,现在我们这个是两个Fragment,我们可以学以致用,写一个BaseFragment基类继承fragment。</p>    <p>到此我们已经动态创建完了,那设置Tag有什么用呢。我们设置标签,当然是为了获取它的对象。假如我们想获取侧边栏的Fragment对象,我们可以这样做:</p>    <p> </p>    <p><code>private static final String FRAGMENT_LEFT_MENU ="fragment_left_menu" ;</code><br> 我们在开头声明一个静态的变量<br> 这样我们在替换的时候就设置好了他们对应的tag值</p>    <p>最后只要写一个函数,通过管理者就可以轻松获得<em>Fragment对象</em>了:</p>    <pre>  <code class="language-java">public Fragment_left_menu getleftMenuFragment(){      //管理者      FragmentManager fm = getSupportFragmentManager();        Fragment_left_menu fragment = (Fragment_left_menu) fm.findFragmentByTag(FRAGMENT_LEFT_MENU);      return fragment;  }</code></pre>    <p>官方是非常推荐我们使用fragment的,所以大家赶紧学起来吧。就好像以前大家都不习惯写Viewholder,现在也被强迫着写了。最后附赠一个思维导图帮助大家理解它:</p>    <p><img src="https://simg.open-open.com/show/77dfeb28381f2c76c32ddb7cf9c06122.png" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="1307" height="652"></p>    <ul>     <li>xutils<br> 工具包:提供一些方便的工具为我们使用,有四大模块:DbUtils,ViewUtils,HttpUtils,BitmapUtils<br> 1.注释<br> 为了解决频繁的<em>findviewbyid</em>,于是就有了这个<strong>ViewUtils</strong>(其实好像也没快多少)<br> 用法(来自官方文档):</li>    </ul>    <pre>  <code class="language-java"> //在Activity中注入:  public void onCreate(Bundle savedInstanceState) {   super.onCreate(savedInstanceState);   setContentView(R.layout.main);   ViewUtils.inject(this);   //注入view和事件 ...   textView.setText("some text...");   ...  }  //在Fragment中注入:  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {   View view = inflater.inflate(R.layout.bitmap_fragment, container, false);   // 加载fragment布局   ViewUtils.inject(this, view);   //注入view和事件 ...  }</code></pre>    <p>最后只需在你需要UI绑定或者事件绑定的语句前加个注释即可如:</p>    <pre>  <code class="language-java">@ViewInject(R.id.textView)  TextView textView;    @OnClick(R.id.test_button)  public void testButtonClick(View v) {   ...}</code></pre>    <p>2.网络工具<br> 我们可以通过HttpUtils请求网络,方便的获取网络数据。<br> 以解析json为例,你可以这样做:</p>    <pre>  <code class="language-java">private void getDatafromServer() {      HttpUtils httpUtils=new HttpUtils();      httpUtils.send(HttpRequest.HttpMethod.GET, murl, new RequestCallBack<String>()   {          public void onSuccess(ResponseInfo<String> responseInfo) {        String result = responseInfo.result;              System.out.println("页签结果是" + result);                parsedata(result);          }        public void onFailure(HttpException error, String msg) {              Toast.makeText(mActivity, msg,Toast.LENGTH_SHORT).show();              error.printStackTrace();          }      });  }</code></pre>    <p>我们首先创建一个<em>HttpUtils</em>对象,在send方法里面,有三个参数分别是(请求的方法(get,post),地址,放回的类型),在匿名内部类中有两个函数:一个是成功,获得数据后,我们可以让它进一步对数据解析。一个是失败,我们可以让他在logcat里打印错误日志并toast出问题在哪。</p>    <p>这个时候我们假设已成功获取了json数据,接着对它进行解析:</p>    <pre>  <code class="language-java">protected void parsedata(String result) {      Gson gson=new Gson();      tabDetaildata = gson.fromJson(result,Tabdata.class);  ....  }</code></pre>    <p>我们借助gson的<em>fromjson</em>方法传入刚获得的结果和一个javabean类,就可以成功解析json数据了。</p>    <blockquote>     <p>注意:getDatafromServer()这个方法是运行在主线程的,所以在里面的方法当然可以随意对UI进行设置</p>    </blockquote>    <p>3.解析网络图片<br> 我们以前常使用线程,异步加载的方式下载网络图片,还要担心内存溢出等问题,现在只需使用<em>BitmapUtils</em>几行代码就可以决解这样复杂的问题。<br> 以<em>listview</em>里加载网络图片为例:</p>    <pre>  <code class="language-java">/*   新闻列表的适配器 */  class NewslistAdapter extends BaseAdapter{   private   BitmapUtils utils;   public  NewslistAdapter(){  utils=new BitmapUtils(mActivity);  utils.configDefaultLoadingImage(R.mipmap.pic_item_list_default);   }  ···  }</code></pre>    <p>我们在内部初始化的时候传入当前上下文(mActivity)声明一个全局的<em>BitmapUtils</em>对象,其中configDefaultLoadingImage方法是用来预加载图片。</p>    <pre>  <code class="language-java">utils.display(hodler.listIcon,tabnewsData.listimage);</code></pre>    <p>最后在适配器的<em>getview</em>函数中,对UI进行设置。它有个display()函数分别填入view和要添加的图片数据,这样就大功告成了。</p>    <ul>     <li>ViewpagerIndicator<br> 1.我们首先介绍的是viewpage的导航指示器:<strong>TabPageIndicator</strong>,比较直观的如图所示(网络图):</li>    </ul>    <p><img src="https://simg.open-open.com/show/b9c50faf7653747ff0b714691bd38562.gif" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="410" height="673"></p>    <p><img src="https://simg.open-open.com/show/6de2280fad482dbc22d2103c6e5cc5a9.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="538" height="323"></p>    <p>如图,我们先把TabPageIndicator的路径拷贝下来,在所在viewpage的xml上添加对应的布局:</p>    <pre>  <code class="language-java"><com.viewpagerindicator.TabPageIndicator      android:id="@+id/indicator"      android:layout_width="match_parent"      android:layout_height="wrap_content"    /></code></pre>    <p>最后,只需在初始view的时候,绑定一下UI</p>    <p><br> <code>myIndicator= (TabPageIndicator)view.findViewById(R.id.indicator);</code><br> 并且在viewpager<strong>添加完适配器之后</strong>,指示器再绑定viewpager</p>    <pre>  <code class="language-java">//添加适配器  mviewPager.setAdapter(new MenudetailAdapter());    viewpagermyIndicator.setViewPager(mviewPager);</code></pre>    <blockquote>     <p>还有一点需要注意的是,如果指示器和viewpager绑定以后,如果viewpager要设置监听事件,这时我们应该对指示器设置而不是viewpager自身。</p>    </blockquote>    <p>2.<strong>CirclePageIndicator</strong><br> 有时我们翻阅图片,为了有个指示的效果:</p>    <p><img src="https://simg.open-open.com/show/0408b924cb26c5d23dde3edbaacd4da5.gif" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="480" height="800"></p>    <p><br> 这时我们就用到CirclePageIndicator<br> 和上面的一样,我们首先在布局文件中将其添加</p>    <p><img src="https://simg.open-open.com/show/2579983ae6902b8e4dac664c3053b5b5.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="614" height="316"></p>    <p>我们看到里面有自定义控件的属性app,所以首先当然把命名空间给引入,在头文件的地方添加</p>    <p><br> <code>xmlns:app="http://schemas.android.com/apk/res-auto"</code><br> 接着初始化,然后绑定viewpager,和上面一样。它有两个方法值得提一下, setSnap()填入(true,false)表示点是否跳跃,也就是我们上方展示图片的效果。onPageSelected()选择初始停留的页面。</p>    <ul>     <li>事件的拦截机制<br> 为了便于理解,我画了一个思维导图</li>    </ul>    <p><img src="https://simg.open-open.com/show/496fbea41cb6b19ae4098f3efa4d5ae0.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="948" height="321"></p>    <p>由<em>Activity</em>把事件分发出去->到了最外层子控件->最外层子控件通过dispatchTouchevent接收分发的事件->传给onInterceptTouchevent,由它决定是否拦截,若拦截就交给onTouchevent对事件进行处理,若不拦截就继续传到内层的子控件->若所有子控件都不处理,则返回给activity处理。<br> 我们现在回到实际的项目中,来讨论事件在具体的例子中如何实现拦截:</p>    <p><img src="https://simg.open-open.com/show/3b1abc1a9f3edf5156b76e2d02c27278.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="822" height="613"></p>    <p><br> 这个app的界面中,最左边有个侧边栏(Sliding menu),最外面是一个由tab来切换的viewpager1,第二层是由viewpagerIndicator来切换的viewpager2,最里面的那层是一个图片切换的Viewpager3。这应该是一个比较复杂的事件拦截的案例了,我们再来画一个清晰的图:</p>    <p><img src="https://simg.open-open.com/show/eeada4331065e435eb138ba0c9c90b5e.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="922" height="501"></p>    <p>我们常用到事件拦截,是在重写一个属于我们自己的自定义view的时候。例如在写最外层的<em>Viewpager1</em>的时候,我们希望它没有滚动这个功能,而是通过tab来控制页面的翻阅,而且我们希望它不拦截事件,而是让内层的viewpager可以接受到事件,就可以在重写的viewpager里这样做:</p>    <pre>  <code class="language-java">//停止滚动  public boolean onTouchEvent(MotionEvent ev) {   return false;}  //事件是否拦截  public boolean onInterceptTouchEvent(MotionEvent ev) {    return false;}</code></pre>    <p>在写viewpager2的时候,遇到了问题,我们发现如果我们往右划的时候,slidingmenu这个父控件会拦截我们的右划事件。所以这个时候我们必须重写一个viewpager类,来规定事件的拦截。</p>    <pre>  <code class="language-java">//请求父类不拦截触摸事件  public boolean dispatchTouchEvent(MotionEvent ev) {      if(getCurrentItem()!=0)    {          getParent().requestDisallowInterceptTouchEvent(true);      }else   {          getParent().requestDisallowInterceptTouchEvent(false);      }      return super.dispatchTouchEvent(ev);  }</code></pre>    <p>我们在事件分发的这个方法中,调用requestDisallowInterceptTouchEvent这个方法,来请求父类不拦截触摸事件,所以当当前的页面不是第一页的时候,我们就不拦截,否则我们拦截。</p>    <p>之后我们写到viewpager3, 我们希望可以自由的划动里面的图片, 所以不希望父类拦截我们的事件,在重写的viewpager里:</p>    <pre>  <code class="language-java">public boolean dispatchTouchEvent(MotionEvent ev) {      getParent().requestDisallowInterceptTouchEvent(true);       return super.dispatchTouchEvent(ev);  }</code></pre>    <p>可是问题就来了,你这里不希望被拦截,但是它的父类viewpager2在第一个页签往右划的时候会规定会被sliding menu拦截,所以我们发现viewpager3在第一个页签往右划的时候还是会引出sliding menu,而不是往前面翻图片。出现了矛盾,这里我们就需要对代码进行重构:我们不给viewpage2重写类,而是给它设置监听(setOnPageChangeListener)</p>    <pre>  <code class="language-java">public void onPageSelected(int position) {        //判断页面 控制slidingmenu是否划出      MainActivity mainActivity= (MainActivity) mActivity;     SlidingMenu slidingMenu = mainActivity.getSlidingMenu();      if(position==0){          slidingMenu.setTouchModeAbove(slidingMenu.TOUCHMODE_FULLSCREEN);      }else{          slidingMenu.setTouchModeAbove(slidingMenu.TOUCHMODE_NONE);      }  }</code></pre>    <p>viewpager3的重写类,我们也做出新的事件拦截规定,我们规定<br> 1.第一个图片往右滑动 跳到上一个页签(或侧边栏)<br> 2.最后一个图片往左滑动 跳到下一个页签<br> 3.向下划的时候<br> 需要拦截,于是:</p>    <pre>  <code class="language-java">public boolean dispatchTouchEvent(MotionEvent ev) {       switch(ev.getAction()){       case MotionEvent.ACTION_DOWN:               //先让父类不拦截子类的事件               getParent().requestDisallowInterceptTouchEvent(true);               //获取子类坐标               startX = (int) ev.getRawX();               startY = (int) ev.getRawY();               break;           case MotionEvent.ACTION_MOVE:              int endX= (int) ev.getRawX();           int endY = (int) ev.getRawY();           //开始分类作比较 左划 右划 下划           if(Math.abs(endX-startX)>Math.abs(endY-startY)){                 //左右划               if(endX>startX){   //右划 第一个页签 就拦截                   if(getCurrentItem()==0){                       getParent().requestDisallowInterceptTouchEvent(false);                   }                }  else{//左划                   if(getCurrentItem()==getAdapter().getCount()-1){      getParent().requestDisallowInterceptTouchEvent(false);                 }               }           }else{                 //下划 拦截               getParent().requestDisallowInterceptTouchEvent(false);           }           break;       default:       break;       }  return super.dispatchTouchEvent(ev);   }</code></pre>    <p>到此我们就管理好了这个复杂的事件拦截家族</p>    <ul>     <li>传递<br> 我们在开头的时候,说到mActivity是充当当前上下文的作用,除此之外我们还可以拿它来传递对象。比如我们要在后面的页签类里,写个函数控制sliding menu的拖出与隐藏,我们前面说过这个函数是toggle。但是我们必须获得一个是<em>sliding menu</em>的对象,但是要获得<em>sliding menu</em>对象,我们首先得获得<em>MainAcitvity</em>的对象。于是这里就要我们灵活的进行对象的传递: <pre>  <code class="language-java">private void toggleSlidingMenu() {     //获得slidingmenu对象      MainActivity mainActivity= (MainActivity) mActiviy;      SlidingMenu slidingMenu = mainActivity.getSlidingMenu();      slidingMenu.toggle();  //切换状态  }</code></pre> 对于数据的传递,每个项目都有可能错综复杂,在android studio里有个很方便的快捷键,帮助我们找到哪些地方用到了这些数据。<br> 选中要查找的目标,按住alt+F7(find usages):</li>    </ul>    <p><img src="https://simg.open-open.com/show/95475042d39bb463096f1733cd6c292c.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="601" height="228"></p>    <p>这个时候我们还可以右键:</p>    <p><img src="https://simg.open-open.com/show/4ce2a48c481f2c1fb6680c9a78a0c385.jpg" alt="GitHub安卓(Android)热门开源资源在项目中的使用及项目总结" width="479" height="81"></p>    <p>跳转到引用的页面,迅速帮助我们定位,这样我们就不会晕头转向了。</p>    <p>以上就是我重点要讲的,如有不足请多多包涵,也希望大家可以支持我,谢谢!</p>    <p><br>  </p>    <p>文/<a href="/misc/goto?guid=4959673399487798636">Jian_LAM</a>(简书)<br>  </p>