HILOLT

Be a better man.


  • 首页

  • 归档

  • 标签

关于android recyclerview的一些版本bug

发表于 2016-11-15

BUG.1

APPCOMPAT_V7_VERSION=23.0.1: recyclerview嵌套recyclerview的时候,内部的recyclerview没有高度。
APPCOMPAT_V7_VERSION=23.1.1: 新版本已经解决这个问题。
如果由于项目原因,不能升级到新版本,可以写个兼容类:

public class FixedLinearLayoutManager extends LinearLayoutManager {
public FixedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("onLayoutChildren", "'meet a IndexOutOfBoundsException in RecyclerView.'");
}
}
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
break;
case View.MeasureSpec.AT_MOST:
break;
case View.MeasureSpec.UNSPECIFIED:
break;
default:
break;
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
break;
case View.MeasureSpec.AT_MOST:
break;
case View.MeasureSpec.UNSPECIFIED:
break;
default:
break;
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec,
int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}

BUG.2

java.lang.IndexOutOfBoundsException: 在使用 recycler.getViewForPosition(position),会出现数组角标越界的crash,原因是recyclerview的数据在不同的线程中被修改,简单的解决办法是加个catch,
这个bug,出现的频率很小,但google至今没有修改。继承LinearLayoutManager,catch住onLayoutChildren。

public class FixedLinearLayoutManager extends LinearLayoutManager {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("onLayoutChildren", "'meet a IndexOutOfBoundsException in RecyclerView.'");
}
}

END

Android Data Binding Library(DBL)分享

发表于 2016-11-07

What`s DBL

Google IO may, 2015,Android M Preview:

  • ASD(Android Support Design)
  • APL(Android Percent Layout)
  • DBL(Data Binding Library)

ASD(Android Support Design)

  • Navigation View 抽屉导航
  • nput Edittext Lable 输入框控件的悬浮标签
  • Floating Action Button 悬浮操作按钮
  • Snackbar 显示在屏幕的底部,包含了文字信息与一个可选的操作按钮,是轻量级的,快速的反馈
  • TabLayout 选项卡,与viewpager一起使用
  • CoordinatorLayout 手势, 以及滚动,Design library中的很多控件都利用了它
  • Collapsing Toolbar 可伸缩折叠的Toolbar

APL(Android Percent Layout)

百分比的layout

What`s DBL

DBL(Data Binding Library),最早是微软提出来的,winphone已经使用多年,这个框架是站在开发者这边的,是开发者更高效系统的开发程序。
原理:遍历contentView得到View数组对象,然后通过数据绑定library生成对应的Binding类,自动的替我们生成了标签里面数据的setter函数。

Typically, the developer will be able to call the subclass's set method directly. For
* example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
* will be generated.

DBL 即MVVM框架

Why DBL

无论是MVC MVP MVVM 模式都是为了隔离样板代码

  • DBL 接管Presenter(MVP),个人认为MVP学习成本比较高,而且耦合度高
  • DBL实现view中数据的所有绑定和更新操作
  • The Data Binding Library offers both flexibility and broad compatibility
  • Model-View-View-Model,View和Model的双向绑定
  • 当数据更新,框架收到通知,视图可以自动更新,View和Model可以做到松耦合
  • 减轻Activity和Fragment的压力
  • 减少代码行数,简洁,易读性,findviewbyid,只需遍历一次
  • Butterknife,Ahibernate,Afinal,注解拖慢解析速度,DBL不会
  • 官方说会提高xml的解析速度
  • 可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性

Use DBL

  • platform: Android 2.1 (API level 7+)
  • dataBinding{
    enabled = true
    }
Activity & Fragment
private MyTestBinding myTestBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (myTestBinding == null) {
// 这样写
myTestBinding = MyTestBinding.inflate(getLayoutInflater());
// 也可以这样
myTestBinding = DataBindingUtil.setContentView(this, R.layout.my_test_layout);
}
}
/*
* {@code Fragment}
*/
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (myTestBinding == null) {
myTestBinding = MyTestBinding.inflate(inflater, container, false);
}
return myTestBinding.getRoot();
}

简单小栗子

public class MyViewModel {
String appName;
String appCode;
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppCode() {
return appCode;
}
public void setAppCode(String appCode) {
this.appCode = appCode;
}
public MyViewModel(String appName, String appCode) {
this.appName = appName;
this.appCode = appCode;
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- model与view进行绑定,data相当于一个通信桥梁,搭建了UI与业务之间的通路-->
<!-- MyTestBinding如果没有自定义命名,则会自动生成以layout命名的binding类-->
<data class="MyTestBinding">
<import type="android.view.View"/>
<!-- MyViewModel viewModel; -->
<variable
name="viewModel"
type="com.baidu.test.MyViewModel"/>
<!-- 也可以这样来写,先导入,再给命名
<import type="com.baidu.test.MyViewModel"/>
<variable name="viewModel" type="MyViewModel" />
-->
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.appName}"
android:textAppearance="@android:style/TextAppearance.Holo.Widget.TextView"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.appCode}"
android:textAppearance="@android:style/TextAppearance.Holo.Widget.TextView"/>
</LinearLayout>
</layout>
*** @{viewModel.appName} == viewModel.appName == viewModel.getAppName() == viewModel.appName() ***
myTestBinding.setViewModel(new MyViewModel("my app name", "8.0"));

如何编写xml

想怎么写就怎么写~

支持的表达式

表达式
Mathematical + - / * %                    算术运算
String concatenation +                字符串连接
Logical && ||                        逻辑运算
Binary & | ^                        二进制
Unary + - ! ~                        一元运算
Shift >> >>> <<                    位运算
Comparison == > < >= <=                关系运算符
instanceof                        类型比较
Grouping ()                        组
Literals - character, String, numeric, null         复数常量
Cast                            类型转换
Method calls                        方法调用
Field access                        字段访问
Array access []                        数组存取
Ternary operator ?:                    三元运算

如何赋值

当然是在java代码里面赋值啦 ~

myTestBinding.setViewModel(new MyViewModel("appname", "8.0"));
myTestBinding.setIsOK(true);
myTestBinding.setIndex(0);
myTestBinding.setName("name");
myTestBinding.setList(new ArrayList<String>() {
{
add("a string");
}
});
myTestBinding.setMyMap(new HashMap<String, String>(){
{
put("key","value");
}
});
// 到这里,是不是发现比findviewById好写多了,有木有!

绑定事件

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
public class MyViewModel {
public void onNameViewClick(View nameView) {
Log.e(TAG, "onNameViewClick");
}

动态赋值实例1:

那么,到现在为止,绑定,编写,赋值,事件,已经Ok了,但是还远远不能满足我们的需求,我们的业务逻辑往往是复杂的。
庆幸的是DataBinding也支持内容观察者,observe,而且使用起来也相当方便。

分3步:

  • 在getter()方法上添加注解:@Bindable
  • Bindable注解会自动生成一个entry类:BR (AS1.5不自动生成,AS2.2自动生成,不知原因)
  • 在setter方法里,通知UI更新数据:notifyPropertyChanged(BR.appName)
public class MyViewModel extends BaseObservable {
public void onNameViewClick(View nameView) {
Log.e(TAG, "onNameViewClick");
setAppName("change name");
}
@Bindable
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
notifyPropertyChanged(BR.appName);
}

动态赋值实例2

其实使用BaseObservable已经基本够用,但是Google还给我们封装了一系列的数据结构Observable Collections,使用起来也更爽一些。主要使用get()和set()取存。

BaseObservable Subclasses
ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableField<T>, ObservableFloat, ObservableInt, ObservableLong,ObservableShort, ObservableArrayMap,ObservableArrayList

动态赋值实例3

ObservableFields的使用方法就更加简单了,例如下面代码:

// layout的写法不变:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
// java 代码就更简单了,因为我们已经不需要getter()和setter()了,有木有~
public class MyViewModel {
public ObservableField<String> appName = new ObservableField<>();
public void onNameViewClick(View nameView) {
appName.set("appname");
appName.get();
}
}

动态赋值实例4

addOnPropertyChangedCallback

dashboard.isMarking.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
if (dashboard.willShowCollectMark.get()
&& !dashboard.isMarking.get()
&& DevController.getInstance().isBatchTrackTesting()) {
onClickMark(marker);
}
}
});

使用view ID

<Button
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
// 其实myTestBinding.btnTest就是View#Button
myTestBinding.btnTest.setText("its a view");

ViewStubProxy

ViewStub , View的嵌套,因为viewstub与其他view不同,viewstub一开始是不可见的,实际上是不存在与view的结构中,所以要监听OnInflateListener,来设置binding。

<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
myTestBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding viewStubBinding = DataBindingUtil.bind(inflated);
viewStubBinding.setViewModel(xxx);
}
});

Include

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name" bind:user="@{user}"/>
<include layout="@layout/contact" bind:user="@{user}"/>
</LinearLayout>
</layout>

注意:name.xml和contact.xml里面必须声明User,另外,不支持merge

ListView

可以看到代码里,没有viewHolder的身影了,我们再也不用写一堆代码了。

listAdapter = new ArrayAdapter<BrtaTestResult>(getActivity(),
R.layout.dev_item_brta_test_result, mockLocation.testResults) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BrtaTestResultBinding binding;
if (convertView == null) {
binding = BrtaTestResultBinding.inflate(LayoutInflater.from(parent.getContext()),
parent, false);
binding.getRoot().setTag(binding);
} else {
binding = (BrtaTestResultBinding) convertView.getTag();
}
binding.setIndex(position);
binding.setItemViewModel(getItem(position));
binding.setListViewModel(mockLocation);
binding.test.setTag(binding);
return binding.getRoot();
}
};
}
ListView listView = testBinding.listView;
listView.setDividerHeight(4);
listView.setAdapter(listAdapter);

RecyclerView

private static class MyHolder extends RecyclerView.ViewHolder {
private MyItemBinding mBinding;
private MyHolder(MyItemBinding binding) { // 构造方法,传入binding
super(binding.getRoot());
mBinding = binding;
}
static MyHolder create(LayoutInflater inflater, ViewGroup parent) {
MyItemBinding binding = MyItemBinding.inflate(inflater, parent, false);
return new MyHolder(binding); // 使用binding inflate item 布局
}
void bindTo(User user) { // 传入model
mBinding.setUser(user);
mBinding.executePendingBindings();
}
}
public MyHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return MyHolder.create(inflater, viewGroup); // 得到holder
}
public void onBindViewHolder(MyHolder myHolder, int position) {
myHolder.bindTo(userList.get(position)); // 获取model
}

Noting is perfect

我们的业务逻辑各种各样,也比较复杂

  • 在xml文件里写表达式的方式,对于view的测试是有些困难的,所以我们可以把一些逻辑写入model类
  • 对于一些复杂的view,显得比较笨重
  • 找不到BR文件,clean,你懂得
  • Android Studio may say: annot resolve symbol , 有些符号还不是支持的很好BR,但是不妨碍编译
  • Android Studio 提示找不到binder,绝大多数是xml写的有问题,没有generate成功object
  • xml里面的变量要全路径书写:java.util.List not List
  • 临时更改xml的时候,编译器会报一些编译的错误,忽略之
  • text不要写入int类型,String.valueOf 很有必要
  • 有的时间BR要写全路径
  • 如果viewmodel中包含{import android.content.Context;} , 那么说明你用错了,内存泄露
  • 出现bug很难定位问题所在,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题

不管mvc,mvp,mvvm都只是个工具,敢于拥抱新的技术,但也不能盲从,如phonegap,jQuerymobile,然并卵

倒计时Demo

/**
* </p>
* Created by liutao18 on 2016/9/30.
*/
public class CountdownViewModel extends BaseObservable {
private static final String TAG = "CountdownViewModel";
private static CountdownViewModel instance;
private CountDownTimer countDownTimer;
private long timeLeftMillis;
private boolean isRunning;
public static CountdownViewModel me() {
if (instance == null) {
instance = new CountdownViewModel();
}
return instance;
}
@Bindable
public long getTimeLeftMillis() {
return timeLeftMillis;
}
public void setTimeLeftMillis(long timeLeftMillis) {
timeLeftMillis = timeLeftMillis;
notifyPropertyChanged((int) timeLeftMillis);
}
public void startTimer(long timeLeftMillis) {
if (!isRunning) {
isRunning = true;
countDownTimer = new CountDownTimer(timeLeftMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
setTimeLeftMillis(millisUntilFinished / 1000);
}
@Override
public void onFinish() {
isRunning = false;
setTimeLeftMillis(0);
}
};
countDownTimer.start();
} else {
Log.i(TAG, "Timer already started, call restart first");
}
}
/**
* 取消倒计时
*/
public void cancelTimer() {
if (isRunning) {
countDownTimer.cancel();
isRunning = false;
}
}
/**
* 重启倒计时
*/
public void restartTimer() {
cancelTimer();
startTimer(60000);
}
}
public class MyTestClassActivity extends Activity {
private CountdownViewBinding binder;
private CountdownViewModel countdownViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (binder == null) {
CountdownViewBinding.inflate(getLayoutInflater());
}
countdownViewModel = CountdownViewModel.me();
binder.setViewModel(countdownViewModel);
binder.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
countdownViewModel.startTimer(60 * 1000);
// countdownViewModel.cancelTimer();
// countdownViewModel.restartTimer();
// countdownViewModel.getTimeLeftMillis();
}
});
}
}
<data class="CountdownViewBinding">
<import type="com.baidu.test.CountdownViewModel"/>
<variable
name="viewModel"
type="CountdownViewModel"/>
</data>

Java中的Atomic包使用指南

发表于 2016-09-08

Atomic类的作用

  • 使得让对单一数据的操作,实现了原子化
  • 使用Atomic类构建复杂的,无需阻塞的代码
  • 访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子单元。

这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。其中的类可以分成4组

AtomicBoolean,
AtomicInteger,
AtomicLong,
AtomicReference

AtomicIntegerArray,
AtomicLongArray

AtomicLongFieldUpdater,
AtomicIntegerFieldUpdater,
AtomicReferenceFieldUpdater

AtomicMarkableReference,
AtomicStampedReference,
AtomicReferenceArray

应用场景:计数器

// 传统
class Counter {
private volatile int count = 0;
public synchronized void increment() {
count++; //若要线程安全执行执行count++,需要加锁
}
public int getCount() {
return count;
}
}
// atomic
class Counter {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
//使用AtomicInteger之后,不需要加锁,也可以实现线程安全。
public int getCount() {
return count.get();
}
}
实例1:原子更新基本类型类
public class AtomicIntegerTest {
static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
//以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
System.out.println(ai.getAndIncrement());
System.out.println(ai.get());
}
}
输出:
1
2
实例2:原子更新数组类
public class AtomicIntegerArrayTest {
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
输出:
3
1
实例3:原子更新引用类型
public class AtomicReferenceTest {
public static AtomicReference<user> atomicUserRef = new AtomicReference</user><user>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicUserRef.set(user);
User updateUser = new User("Shinichi", 17);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
输出:
Shinichi
17
实例4:原子更新字段类
public class AtomicIntegerFieldUpdaterTest {
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
.newUpdater(User.class, "old");
public static void main(String[] args) {
User conan = new User("conan", 10);
System.out.println(a.getAndIncrement(conan));
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
输出:
10
11

Hello World

发表于 2016-09-08

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

$ hexo new "My New Post"

More info: Writing

Run server

$ hexo server

More info: Server

Generate static files

$ hexo generate

More info: Generating

Deploy to remote sites

$ hexo deploy

More info: Deployment

renault118@gmail.com

renault118@gmail.com

Be a better man.

4 日志
6 标签
© 2016 renault118@gmail.com
由 Hexo 强力驱动
主题 - NexT.Mist