不荒不慌

圆圆圈圈


  • Home

  • Archives

  • About

Kotlin学习记录(一)

Posted on 2019-06-15 |

1.when语句

如果when语句的每个分支都会返回值,如下:

1
2
3
4
5
6
7
8
fun fact(n:Int):Int {
var result
when (n) {
0, 1 -> result = 1
else -> result = n * fact(n - 1)
}
return result
}

那可以写成这种形式

1
2
3
4
5
6
fun fact(n:Int):Long {
return when (n) {
0, 1 -> 1
else -> n * fact(n - 1)
}
}

在 Kotlin里,if,when,try都可以是一个表达式,返回一个值;表达式可以是一个语句块,返回值是最后一个语句的值。


2. Kotlin中return语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return语句直接返回最近的外层函数,比较下面两个代码片段:

1
2
3
4
5
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach {
if (it == 3) return
println(it)
}

输出:1 2

1
2
3
4
5
6
7
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach (
fun (a:Int) {
if (a == 3) return
println(a)
}
)

输出:1 2 4 5


3. object对象和伴生对象
Kotlin中没有静态属性和方法,可以使用object声明一个object单例对象
:

1
2
3
4
5
6
object User {
val username: String = "brick"
fun say() {
println("Hello!")
}
}

伴生对象,顾名思义就是和一个类相伴而生的对象,它和包含它的外部类是一一对应的,通过外部类的类名来访问它的属性方法,类似于Java里访问类的静态属性和方法:
一般使用方法:

1
2
3
4
5
6
7
8
9
class NumberTest {
companion object Obj{
val flag = false

fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
}
}

这里为NumberTest类定义了伴生对象Obj,Obj这个名字可有可无,Kotlin本身访问伴生对象时不关心这个名字:

1
println("Hello World!!"+ NumberTest.plus(1,2))

不过Java代理访问时需要用到这个名字,如果没有要用Companion代替:

1
2
System.out.println("HelloWorld: "+NumberTest.Obj.getFlag());
System.out.println("HelloWorld: "+NumberTest.Companion.getFlag());

如果想让Java访问形式和Kotlin的完全一致,可以用到const关键字或者@JvmField、@JvmStatic注解来修饰属性方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
class NumberTest {
companion object Obj{
const val flag = false

@JvmField
var name = "brick"
@JvmStatic
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
}
}

1
2
System.out.println("HelloWorld: "+NumberTest.flag);
System.out.println("HelloWorld: "+NumberTest.plus(1, 2));

4.函数式编程的焦点是数据的映射;命令式编程的焦点是解决问题的步骤。

函数式编程是转换数据而非修改原始数据

C++容器——迭代器使用的一个陷阱

Posted on 2019-05-04 |
  • 在项目开发中遇到一个很诡异的bug:使用迭代器循环删除一个std::set容器里的几个元素,但最后发现容器里的对应元素并没有被删除干净,为了说明这个场景,我们来看一个代码片段,这是我为了重现该问题,弄了的一段测试代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int numList[6]={1,2,2,3,3,3};
    //1.set add
    set<int> numSet;
    for(int i=0;i<6;i++)
    {
    //2.1insert into set
    numSet.insert(numList[i]);
    }
    //2.travese set
    for(set<int>::iterator it=numSet.begin() ;it!=numSet.end();it++)
    {
    cout<<*it<<" occurs "<<endl;
    }
    cout<<"before delete numSet.size()= "<<numSet.size()<<endl;
    for(set<int>::iterator it = numSet.begin(); it != numSet.end(); it++)
    {
    cout<<"delete " <<*it <<endl;
    numSet.erase(it);
    }
    cout<<"after delete numSet.size()= "<<numSet.size()<<endl;

    这段测试代码的功能很简单,构造一个set容器,把几个数字塞进去,然后通过它的迭代器循环清除里面的元素,大家可以想一下for循环前后的这两句log输出的是什么。

  • 这是输出的结果

    1
    2
    3
    before delete numSet.size()= 3
    delete 1
    after delete numSet.size()= 2
  • 是不是和你们很多人预想的不太一样,for循环只循环了一次就跳出去了,那说明第一次循环结束再回到for循环条件判断时it!=umSet.end()不成立了,it指向了容器的end,验证一下。

    1
    2
    3
    4
    5
    for(set<int>::iterator it=numSet.begin();(cout<< (it == numSet.end() ? "end":"not end")<<endl, it!=numSet.end());it++) {
    cout<<"delete " <<*it <<endl;
    numSet.erase(it);
    cout<< (it == numSet.end() ? "end":"not end")<<endl;
    }

    输出结果:

    1
    2
    3
    4
    5
    6
    before delete numSet.size()=3
    not end
    1 delete
    not end
    end
    after delete numSet.size()=2

    确实是这样,在网上查阅了一下使用迭代器删除元素的方法,有两条注意事项:

    1.对于关联容器(如map,set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前的iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入,删除一个结点不会对其他结点造成影响。

  • 这样对于关联容器,正确的使用迭代器的方法是这样:

    1
    2
    3
    4
    for(set<int>::iterator it=numSet.begin(); it!=numSet.end());) {
    cout<<"delete " <<*it <<endl;
    numSet.erase(it++);
    }

    2.对于序列式容器(如vector,deque,list等),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。不过erase方法可以返回下一个有效的iterator。

  • 而对于序列容器,正确的方法应该是这样:
    1
    2
    3
    4
    for(set<int>::iterator it=numSet.begin(); it!=numSet.end());) {
    cout<<"delete " <<*it <<endl;
    it = numSet.erase(it);
    }

就是这么一个很小的点,都怪平时使用的时候太过于理所当然了,迭代器的使用还是要小心,此为记。

Android Wear(Wear OS)开发

Posted on 2019-04-14 |

——简化Wear版Wx开发过程中的一些总结

1. 微光模式

智能手表的电池容量都比较低,导致续航也比较短。为了延长手表的续航,Wear OS 手表在没有操作一段时间后,会进入微光模式 AmbientMode。微光模式就是一个省电模式,这个模式会在低功耗下运行,默认情况下,手表会离开当前的应用,返回到表盘的界面。但有时候,我们希望开发的应用在某些情况下,可以一直保持可见的状态,这就需要我们使用支持微光模式的 Activity 了。

  • 一般wear应用的activity继承自WearableActivity,然后activity里调用setAmbientEnabled()来支持微光模式,但是我们的工程的页面架构是一个activity+多fragment的形式;这要求我们的activity必须继承自FragmentActivity,因而需要使用另一种方式来支持微光模式: HomeActivity继承FragmentActivity,同时实现AmbientModeSupport.AmbientCallbackProvider接口,在onCreate里调用AmbientModeSupport.attach(this),它返回一个AmbientModeSupport.AmbientController对象,可用于查询当前微光模式所处的状态。

    1
    public class HomeActivity extends FragmentActivity implements AmbientModeSupport.AmbientCallbackProvider
    1
    2
    3
    4
    5
    6
    7
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    Log.d(TAG, "onCreate");
    AmbientModeSupport.attach(this);
    }

    AmbientModeSupport.AmbientCallbackProvider定义如下,我们可以实现这些接口,来对微光模式的不同状态进行处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public abstract static class AmbientCallback {
    public AmbientCallback() {
    }

    public void onEnterAmbient(Bundle ambientDetails) {
    }

    public void onUpdateAmbient() {
    }

    public void onExitAmbient() {
    }

    public void onAmbientOffloadInvalidated() {
    }
    }

2. Wear OS右划退出

  • wear针对穿戴设备的操作特点,有个默认的手势:右划退出当前全屏Activity,这对于一般页面操作很方便,ios和Android应用很多都有类似的支持,但对于手表这种屏幕比较小设备,wear提供的是全屏响应手势的,并不像手机是左侧边缘才响应,这样wear的交互设计上一般不建议再使用水平滑动的手势,但凡事总有例外,我们的应用中就有一个右划的手势操作,解决方案就是使用SwipeDismissFrameLayout来包裹我们的视图,我们的视图需要重写canScrollHorizontally方法,返回true,这时就启动了边缘滑动状态(屏幕左侧10%的位置才相应水平滑动手势)。
    -
    布局文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <android.support.wear.widget.SwipeDismissFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <com.tencent.ui.voip.SwipeViewContainer
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--我们自己的视图-->
    ...
    </com.tencent.ui.voip.SwipeViewContainer>
    </android.support.wear.widget.SwipeDismissFrameLayout>
  • 自定义View SwipeViewContainer,canScrollHorizontally返回true。

    1
    2
    3
    4
    5
    6
    7
    8
    public class SwipeViewContainer extends RelativeLayout {
    ....

    @Override
    public boolean canScrollHorizontally (int direction) {
    return true;
    }
    }
  • 右划退出在实际使用过程中发现一个问题,被划出的页面(Fragment)退出后又会再闪现了一下,造成很不好的体验,在网上没有找到有用的信息,只能自己摸索。

    • 首先想到的是不是fragment的切换动画引起的, FragmentTransaction定义了如下几种切换方式,但即使设置成TRANSIT_NONE,还是依然会有问题,看来不是切换动画的问题。

      1
      2
      3
      4
      5
      6
      7
      public static final int TRANSIT_ENTER_MASK = 4096;
      public static final int TRANSIT_EXIT_MASK = 8192;
      public static final int TRANSIT_UNSET = -1;
      public static final int TRANSIT_NONE = 0;
      public static final int TRANSIT_FRAGMENT_OPEN = 4097;
      public static final int TRANSIT_FRAGMENT_CLOSE = 8194;
      public static final int TRANSIT_FRAGMENT_FADE = 4099;
    • 然后想能不能在fragment切换过程中对旧页面进行一些操作,这就需要对滑动操作的过程进行监听,SwipeDismissFrameLayout类提供了实现的方法,让我们的视图包裹在SwipeDismissFrameLayout里,然后设置回调SwipeDismissFrameLayout.Callback,在事件的回调方法里进行相关处理。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      private final SwipeDismissFrameLayout.Callback mCallback =
      new SwipeDismissFrameLayout.Callback() {
      public void onDismissed(SwipeDismissFrameLayout layout) {
      Log.d(TAG, "onDismissed()");
      //To do
      }
      public void onSwipeStarted(SwipeDismissFrameLayout layout) {
      Log.d(TAG, "onSwipeStarted()");
      }
      public void onSwipeCanceled(SwipeDismissFrameLayout layout) {
      Log.d(TAG, "onSwipeCanceled()");
      }
      };
      public View onCreateView(@NonNull LayoutInflater inflater, @LayoutRes int layoutid) {
      SwipeDismissFrameLayout swipeLayout = new SwipeDismissFrameLayout(getActivity());
      inflatedView = inflater.inflate(layoutid, swipeLayout, false);

      swipeLayout.addView(inflatedView);
      swipeLayout.addCallback(mCallback);
      return swipeLayout;
      }

      一开始想在onDismissed回调里将当期fragment隐藏(如下),但仍没有任何用处,即使在onSwipeStarted调用没任何作用,最后直接来最暴力的,将当期fragment的View直接设置成GONE,这样闪现的问题就解决了。

      1
      2
      3
      4
      //不行
      getActivity().getSupportFragmentManager().beginTransaction().hide(curFragment);
      //搞定
      inflatedView.setVisibility(View.GONE);

3.Fragment

  • 应用中多个frament彼此进行切换,要求右划后可以回到上一个fragment,要使用add而不是replace,这里需要操作fragment的回退栈。

    进入

    1
    2
    3
    4
    5
    FragmentTransaction transaction =  getActivity().getSupportFragmentManager().beginTransaction();
    transaction.setCustomAnimations(R.anim.slide_right_in, R.anim.slide_right_out);
    transaction.add(R.id.fragment_container, messageListFragment);
    transaction.addToBackStack(null);
    transaction.commit();

    回退

    1
    getActivity().getSupportFragmentManager().popBackStack();

    同一个fragment不能重复add,不然会出错,这里需要处理一下

    1
    2
    3
    if (messageFragment.isAdded()){
    transaction.remove(messageFragment);
    }
  • 使用回退的方式进入原来的fragment,是不会调用Fragment的任何生命周期回调的,那我们有时候原页面又需要知道这个操作,比如需要更新一下页面的数据或状态等,那我们就需要手动去监听,那监听源是什么呢,我们想到这里发生改变的全局数据就是我们的回退栈,而FragmentManager刚好也提供监听回退栈变化的listener,我们重写listener里的onBackStackChanged接口,在里面我们找到要进入的fragment,然后主动触发它的生命周期方法,比如onResume。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    FragmentManager mgrFragment = getSupportFragmentManager();
    mgrFragment.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
    FragmentManager manager = getSupportFragmentManager();
    if (fragmentStackSize > manager.getBackStackEntryCount()) {//判断是回退操作
    if (manager != null) {
    Fragment currFrag = manager.findFragmentById(R.id.fragment_container);
    currFrag.onResume();
    }
    }
    fragmentStackSize = manager.getBackStackEntryCount();
    }
    });

4.性能问题

应用里有用RecycleView来显示列表,列表里有图片,我们使用Glide来显示的,这里需要注意的一点是,在使用Glide的时候,应该在别处对其进行一下初始化,不要在onBindViewHolder里就直接使用,这会导致第一次打开页面很慢。同样道理,onBindViewHolder里用到东西,最好都事先加载好。图片可以根据实际需求进行剪裁,避免使用原图。

1
2
3
4
RequestOptions options = new RequestOptions();
options.centerCrop();
options.override(width, height);
Glide.with(mContext).load(conversation.getHeadimagepath()).into(holder.avatar);

5.总结

以上就是这次这个小项目开发过程中的一些点,并没有高大上的东西,自己做个记录,如果能帮助到你那就更好了

Hello World

Posted on 2019-04-06 |

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Brick Zhang

Brick Zhang

4 posts
RSS
GitHub
© 2019 Brick Zhang
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4
访客数 人 总访问量 次