修改bitmap透明度,实现视频贴纸透明度动画,实现方法:

  1. canvas 修改paint的alpha,然后绘制在一个新的bitmap。
  2. 修改图片的A通道。

Canvas

1
2
3
4
5
6
7
8
9
10
public Bitmap setBitmapAlpha(Bitmap bitmap, int alpha) {
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);

Paint paint = new Paint();
paint.setAlpha(alpha);
//绘制到新的bitmap
canvas.drawBitmap(bitmap, 0, 0, paint);

bitmap.recycle();
return newBitmap;

}
阅读全文 »

  直播礼物上线以来,礼物越来越复杂,由最开始的静态图、小动画、全屏动画,动画越来越复杂,帧数也越来越多,文件也越来越大;从开始的效果放在apk到现在服务端可配;为了不影响用户体验,觉定开始优化直播礼物效果,优化先要找出问题所在;当动画是全屏动画,绘制和解码都耗费大量cpu,大文件的全屏动画绘制直接导致主线程卡顿,而且绘制cpu的开销增加导致解码更慢,在低端机都无法观看。

  • 优化方向
  1. 优化动画文件,加快解码速度。
  2. 优化解码,使用bitmap缓存池,减少GC。
  3. 使用GPU绘制。

使用fresco渲染

  由于项目一直使用fresco加载图片,礼物上线后也使用fresco加载(网络下载缓存、解码显示),直播礼物动画把序列帧压成webp文件。
  查看fresco源码后,fresco在绘制当前帧时,再去预加载下一帧,当帧间隔时间较短或者解码较慢,预加载缓存就无效,导致了跳帧。

阅读全文 »

在直播间点赞动画,通过在父控件addView,然后属性动画改变view位置、大小以透
明度,从而实现点赞动画,当大量点赞时,addview引起重新布局、属性动画都十分耗时,造成卡顿。从未使用子线程绘制,避免主线程卡顿。优化方式主要有三种:View绘制,TextureView,SurfaceView,然后定时刷新帧,计算当前帧赞的位置、大小、透明度。

View绘制

重写ondraw,定时postInvalidateDelayed刷新UI,然后遍历绘制当前帧每一个赞。

  1. 优点
    兼容性较好
  2. 缺点
    绘制比较耗时

SurfaceView

surfaceView定时刷新赞动画,和view绘制最大区别是可以放在子线程绘制

surfaceView生命周期

  • surfaceCreated(SurfaceHolder holder)
    surface可用,创建绘制线程,设置view微透明。
    getHolder().setFormat(PixelFormat.TRANSPARENT);

  • surfaceDestroyed(SurfaceHolder holder)
    surface销毁 释放资源,终止绘制线程。

绘制

先等待surface可用,通过getHolder().lockCanvas()方法获取当前canvas,遍历绘制当前帧,最后getHolder().unlockCanvasAndPost(canvas)提交canvas刷新UI

1
2
3
4
5
6
7
8
9
10
11
12
13
startTime = SystemClock.uptimeMillis();
renderLock.lock();
if (mHasSurface) {
if (getHolder().getSurface().isValid()) {
canvas = getHolder().lockCanvas();

if (canvas != null) {
clearCanvas(canvas);
drawLikes(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
}

阅读全文 »

礼物连击效果,显示当前连击数,有缩放动画。

绘制数字

由于数字有描边,需要自定义控件,描边颜色不同于填充颜色

  1. 画笔初始化
    一个Paint为数字,另一个Paint为数字描边

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //stroke
    strokePaint = new Paint();
    strokePaint.setColor(strokeColor);
    strokePaint.setTextSize(textSize);
    strokePaint.setTypeface(Typeface.DEFAULT_BOLD);
    strokePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    strokePaint.setStrokeWidth(strokeWidth);
    strokePaint.setAntiAlias(true);
    strokePaint.setAlpha(76);
    strokePaint.setStrokeJoin(Paint.Join.ROUND);

    //number
    textPaint = new Paint();
    textPaint.setColor(textColor);
    textPaint.setTextSize(textSize);
    textPaint.setTypeface(Typeface.DEFAULT_BOLD);
    textPaint.setAntiAlias(true);
  2. 计算绘制区域
    计算绘制区域宽高,开始x,y坐标。

  3. 绘制
    阅读全文 »

状态通常有三种实现方式:常量、枚举、注解

  • 常量:简单,便于位运算,占用内存少,但是携带信息少,可读性差。
  • 枚举:便于封装,保证了类型安全,可读性高,占用内存大,dex变大
    ps.Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
  • 注解:相对常量比较安全,编译期就可以排除错误类型
    礼物控件用来播放礼物,从出现到消失共有8种状态(空闲、加载资源、进入中、播放礼物效果、连击动画、离开中、活跃的),多种状态是可以并行的,所以通过flag标记,一位表示一种状态,然后使用注解确保值的安全。
  1. @IntDef
    flag 是否是flag, value 枚举指定值,避免值的重复
  2. @Retention(RetentionPolicy.SOURCE)
    注解将在编译丢弃
阅读全文 »

实现一个圆形自定义进度条,进度条的颜色随着进度变化而变化,按进度下去后反向回去重置;为了解耦,主要有2个模块:1、自定义View,2、Controler

自定义view

提供进度、进度条颜色、背景颜色等设置接口,负责UI绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

final int width = getWidth();
final int height = getHeight();

float delta = Math.max(finishedStrokeWidth, unfinishedStrokeWidth) / 2;
finishedOuterRect.set(delta,
delta,
width - delta,
height - delta);
unfinishedOuterRect.set(delta,
delta,
width - delta,
height - delta);

float innerCircleRadius = (width - 2 * Math.min(finishedStrokeWidth, unfinishedStrokeWidth) + Math.abs(finishedStrokeWidth - unfinishedStrokeWidth)) / 2f;
//背景圆
canvas.drawCircle(width / 2.0f, height / 2.0f, innerCircleRadius, innerCirclePaint);

int startingDegree = getStartingDegree();
float progressAngle = getProgressAngle();

canvas.drawArc(unfinishedOuterRect, startingDegree + progressAngle, 360 - progressAngle, false, unfinishedPaint);
canvas.drawArc(finishedOuterRect, startingDegree, progressAngle, false, finishedPaint);

//文字
if (!TextUtils.isEmpty(text)) {
float textHeight = textPaint.descent() + textPaint.ascent();
canvas.drawText(text, (getWidth() - textPaint.measureText(text)) / 2.0f, (getWidth() - textHeight) / 2.0f, textPaint);
}
}

阅读全文 »

最近在做直播礼物,需要展示礼物动画,调研的方案有 帧动画,gif,apng,视频,webp,多次试验权衡后,确定最佳方案使用webp动画。
!--[移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)--

PNG帧动画

png简介

诞生在 1995 年,比 JPEG 晚几年。它本身的设计目的是替代 GIF 格式,所以它与 GIF 有更多相似的地方。PNG 只支持无损压缩,所以它的压缩比是有上限的。相对于 JPEG 和 GIF 来说,它最大的优势在于支持完整的透明通道。

帧动画实现

通过系统的AnimationDrawable类播放帧动画,用BitmapDrawable加载bitmap作为一帧,播放结束后主动recycle,释放bitmap。

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void initAnim(final Context context) {
if (TextUtils.isEmpty(dirPath)) {
return;
}
try {
AssetManager assetManager = context.getAssets();
String[] framePaths = assetManager.list(dirPath);
int frameDuration = duration != 0 ? duration / framePaths.length : FRAME_DURATION;
for (String frame : framePaths) {
String framePath = dirPath + "/" + frame;
Bitmap frameBmp = BitmapFactory.decodeStream(assetManager.open(framePath));
addFrame(new BitmapDrawable(context.getResources(), frameBmp), frameDuration);
}
addFrame(getFrame(0), 0);
} catch (Exception e) {
e.printStackTrace();
}
}

private void destroyBitmap() {
try {
for (int i = 0; i < getNumberOfFrames(); i++) {
Drawable frame = getFrame(i);
if (frame instanceof BitmapDrawable) {
Bitmap bmp = ((BitmapDrawable) frame).getBitmap();
if (bmp != null && !bmp.isRecycled()) {
bmp.recycle();
}
}
frame.setCallback(null);
}
setCallback(null);
} catch (Exception e) {
e.printStackTrace();
}
}

  • 遇到的问题
  1. 性能问题:内存开销大,播放一个全屏动画(10帧,750p),内存涨了40MB,gc频繁
  2. 释放问题:为了避免OOM,动画执行完后,主动释放bitmap,调用stop后动画仍在绘制,导致抛异常,加回调后,部分机型仍会抛异常。
  3. 稳定性:由于自己封装,边界问题考虑不是很好,容易卡死。
  4. 文件太大
    阅读全文 »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Rectangle {
public static final int OUT_LEFT = 1;

public static final int OUT_TOP = 2;

public static final int OUT_RIGHT = 4;

public static final int OUT_BOTTOM = 8;
private float width;
private float height;
private float left;
private float top;

public Rectangle(float x1, float y1, float w, float h) {
this.left = x1;
this.top = y1;
width = w;
height = h;
}

public boolean intersectsLine(float x1, float y1, float x2, float y2) {
int out1, out2;
if ((out2 = outcode(x2, y2)) == 0) {
return true;
}
while ((out1 = outcode(x1, y1)) != 0) {
if ((out1 & out2) != 0) {
return false;
}
if ((out1 & (OUT_LEFT | OUT_RIGHT)) != 0) {
float x = left;
if ((out1 & OUT_RIGHT) != 0) {
x += width;
}
y1 = y1 + (x - x1) * (y2 - y1) / (x2 - x1);
x1 = x;
} else {
float y = top;
if ((out1 & OUT_BOTTOM) != 0) {
y += height;
}
x1 = x1 + (y - y1) * (x2 - x1) / (y2 - y1);
y1 = y;
}
}
return true;
}

public int outcode(double x, double y) {
int out = 0;
if (this.width <= 0) {
out |= OUT_LEFT | OUT_RIGHT;
} else if (x < this.left) {
out |= OUT_LEFT;
} else if (x > this.left + this.width) {
out |= OUT_RIGHT;
}
if (this.height <= 0) {
out |= OUT_TOP | OUT_BOTTOM;
} else if (y < this.top) {
out |= OUT_TOP;
} else if (y > this.top + this.height) {
out |= OUT_BOTTOM;
}
return out;
}
}

初始化

获取文件大小,已下载文件大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void init() throws Exception {
URL url = new URL(httpUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
fileSize = connection.getContentLength();
connection.disconnect();

Context context = NiceApplication.getApplication().getApplicationContext();
File fileDir = StorageUtils.getIndividualFileDirectory(context, "nice-lens-resource");
rootDir = fileDir.getAbsolutePath();
File file = new File(fileDir, fileName + ".zip");
if (!file.exists()) {
file.createNewFile();
completeSize = 0;
} else {
completeSize = file.length();
}
zipFilePath = file.getAbsolutePath();
}

断点下载

设置http头,指定下载开始和结束点,”Range”->”bytes=startPosi-endPosi”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private String download() throws Exception {
if (completeSize == fileSize) {
return zipFilePath;
}

HttpURLConnection connection;
RandomAccessFile randomAccessFile;
InputStream is;
URL url = new URL(httpUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
// 设置范围,格式为Range:bytes x-y;
connection.setRequestProperty("Range", String.format("bytes=%s-%s", completeSize, fileSize));

randomAccessFile = new RandomAccessFile(zipFilePath, "rwd");
randomAccessFile.seek(completeSize);
is = connection.getInputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, length);
completeSize += length;
update();
}
return zipFilePath;
}

解压zip

主要使用ZipInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static void unZipFolder(String zipFilePath, String outputDir) throws Exception {
FileUtils.deleteFile(outputDir);

ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry zipEntry;
String szName = "";
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();

if (szName.contains("__MACOSX")) {
continue;
}

if (zipEntry.isDirectory()) {
// get the folder name of the widget
szName = szName.substring(0, szName.length() - 1);
File folder = new File(outputDir + File.separator + szName);
folder.mkdirs();
} else {
File file = new File(outputDir + File.separator + szName);
file.createNewFile();
// get the output stream of the file
FileOutputStream out = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
// read (len) bytes into buffer
while ((len = inZip.read(buffer)) != -1) {
// write (len) byte from buffer at the position 0
out.write(buffer, 0, len);
out.flush();
}
out.close();
}
}
inZip.close();
}

常用adb命令:
ps: 其中adb shell * 的命令实际上都是 linux命令,只是前面加了adb shell就是执行一次,而不会进入到adb shell下面。

  1. 获取序列号:
    adb get-serialno
  2. 查看连接计算机的设备:
    adb devices
  3. 重启机器:
    adb reboot
  4. 重启到bootloader,即刷机模式:
    adb reboot bootloader
  5. 重启到recovery,即恢复模式:
    adb reboot recovery
  6. 查看log:
    adb logcat //查看所有logadb logcat -s demo1 //输出tag 为demo1的logadb logcat > log //将日志输出到log文件里面去
  7. 终止adb服务进程:
    adb kill-server
  8. 重启adb服务进程:
    adb start-server
  9. 获取机器MAC地址:
    adb shell cat /sys/class/net/wlan0/address
  10. 获取CPU序列号:
    adb shell cat /proc/cpuinfo
  11. 安装APK:
    adb install //比如:adb install baidu.apk
  12. 保留数据和缓存文件,重新安装apk:
    adb install -r //比如:adb install -r baidu.apk
  13. 安装apk到sd卡:
    adb install -s // 比如:adb install -s baidu.apk
  14. 卸载APK:
    adb uninstall //比如:adb uninstall com.baidu.search
  15. 卸载app但保留数据和缓存文件:
    adb uninstall -k //比如:adb uninstall -k com.baidu.search
  16. 启动应用:
    adb shell am start -n /.
  17. 查看设备cpu和内存占用情况:
    adb shell top
  18. 查看占用内存前6的app:
    adb shell top -m 6
  19. 刷新一次内存信息,然后返回:
    adb shell top -n 1
  20. 查询各进程内存使用情况:
    adb shell procrank
  21. 杀死一个进程:
    adb shell kill [pid]
  22. 查看进程列表:
    adb shell ps
  23. 查看指定进程状态:
    adb shell ps -x [PID]
  24. 查看后台services信息:
    adb shell service list
  25. 查看当前内存占用:
    adb shell cat /proc/meminfo
  26. 查看IO内存分区:
    adb shell cat /proc/iomem
  27. 将system分区重新挂载为可读写分区:
    adb remount
  28. 从本地复制文件到设备:
    adb push
  29. 从设备复制文件到本地:
    adb pull
  30. 列出目录下的文件和文件夹,等同于dos中的dir命令:
    adb shell ls
  31. 进入文件夹,等同于dos中的cd 命令:
    adb shell cd
  32. 重命名文件:
    adb shell rename path/oldfilename path/newfilename
  33. 删除system/avi.apk:
    adb shell rm /system/avi.apk
  34. 删除文件夹及其下面所有文件:
    adb shell rm -r
  35. 移动文件:
    adb shell mv path/file newpath/file
  36. 设置文件权限:
    adb shell chmod 777 /system/fonts/DroidSansFallback.ttf
  37. 新建文件夹:
    adb shell mkdir path/foldelname
  38. 查看文件内容:
    adb shell cat
  39. 查看wifi密码:
    adb shell cat /data/misc/wifi/*.conf
  40. 清除log缓存:
    adb logcat -c
  41. 查看bug报告:
    adb bugreport
  42. 获取设备名称:
    adb shell cat /system/build.prop
  43. 查看ADB帮助:
    adb help

有root

手机安装adb wifi,root授权。

无root

先用数据线连接电脑,打开命令行输入

1
adb tcpip 5555

确保手机连接的wifi和电脑是同一个局域网

1
adb connect *.*.*.*

1. Theme

1
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >

优点

  • 容易记住,不容易犯错
  • ui更加平滑,因为系统知道全屏信息

2. WindowManager flags

1
2
3
4
5
6
// FullScreen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Cancel
getWindow().setFlags(~WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

全屏切换时,布局会重新拉伸,造成卡顿,避免方法:

  • 通过FLAG_LAYOUT_IN_SCREEN flag使全屏时布局在状态栏以下。
  • FLAG_LAYOUT_NO_LIMITS flag使全屏时布局全屏
    FLAG_LAYOUT_NO_LIMITS flag会导致系统函数getWindowVisibleDisplayFrame(主要用来计算键盘高度)失效,致命bug。

Flag bug避免与优化
使用marginTop避免Resize卡顿:
不使用有FLAG_LAYOUT_NO_LIMITS flag,给全屏展示的rootview设置一个marginTop,为负的状态栏高,切换过程看起来就像拉伸至全屏,当全屏后去掉marginTop。

虽然解决掉不是有FLAG_LAYOUT_NO_LIMITS 的bug,但是状态栏收缩回去会卡白一下。

3. SetSystemUiVisibility()

API 16以上

1
2
3
4
5
6
7
8
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
// Remember that you should never show the action bar if the
// status bar is hidden, so hide that too if necessary.
ActionBar actionBar = getActionBar();
actionBar.hide();

使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN使布局拉伸至全屏,使用SYSTEM_UI_FLAG_LAYOUT_STABLE 避免布局resize

Immersive(退出全屏后,状态栏自动隐藏)

  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY API 19

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    getWindow().getDecorView().setSystemUiVisibility(
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    | View.SYSTEM_UI_FLAG_FULLSCREEN);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    getWindow().getDecorView().setSystemUiVisibility(
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    | View.SYSTEM_UI_FLAG_FULLSCREEN);
    } else {
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
  • 监听window,实现API19以下的Immersive。

    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    //Immersive
    if (hasFocus && isFullScreen) {
    setFullScreen(isFullScreen);
    }
    }

缺点:弹出键盘返回桌面都自动退出全屏。

4. WindowManager和SetSystemUiVisibility混合使用(完美方案)

  • 布局全屏,避免全屏切换闪烁。

    1
    2
    3
    4
    5
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN){
    getWindow().getDecorView().setSystemUiVisibility(
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }

  • 全屏切换设置

    1
    2
    3
    4
    5
    //全屏
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN);
    //退出全屏
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

官方文档
Managing the System UI
Hiding the Status Bar

使用camera拍照基本流程

  1. Obtain an instance of Camera from open(int).
    获取相机实例,open指定前后摄像头。
  2. Get existing (default) settings with getParameters().
    通过getParameters获取相机默认设置参数
  3. If necessary, modify the returned Camera.Parameters object and call setParameters(Camera.Parameters).
    setParameters设置相机参数
  4. Call setDisplayOrientation(int) to ensure correct orientation of preview.
    setDisplayOrientation设置预览图像旋转角度
  5. Important: Pass a fully initialized SurfaceHolder to setPreviewDisplay(SurfaceHolder). Without a surface, the camera will be unable to start the preview.
    显示预览到surfaceview,没有surface相机是不可以预览。
  6. Important: Call startPreview() to start updating the preview surface. Preview must be started before you can take a picture.
    startPreview开始预览,拍照前一定要预览。
  7. When you want, call takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) to capture a photo. Wait for the callbacks to provide the actual image data.
    takePicture拍照
  8. After taking a picture, preview display will have stopped. To take more photos, call startPreview() again first.
    拍照后预览将会停止,重拍一定要调用startPreview。
  9. Call stopPreview() to stop updating the preview surface.
    调用stopPreview停止预览
  10. Important: Call release() to release the camera for use by other applications. Applications should release the camera immediately in onPause() (and re-open() it in onResume()).
    调用release释放相机。
阅读全文 »

RxJava 的观察者模式

RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。

与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() / onEvent())之外,还定义了两个特殊的事件:onCompleted() 和 onError()。

  • onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的onNext() 发出时,需要触发 onCompleted() 方法作为标志。
  • onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
  • 在一个正确运行的事件序列中, onCompleted() 和 onError() 有且只有一个,并且是事件序列中的最后一个。需要注意的是,onCompleted() 和 onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

create() 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列,例如:

  • just(T…): 将传入的参数依次发送出来。
    Observable observable = Observable.just(“Hello”, “Hi”, “Aloha”);
  • from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
阅读全文 »

ViewPager的更新通过的PagerAdapter中的notifyDataSetChanged方法,可以增加、删除或排序,但不可以像RecycleView更新view。
官方文档:
PagerAdapter supports data set changes. Data set changes must occur on the main thread and must end with a call to {@link #notifyDataSetChanged()} similar to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data set change may involve pages being added, removed, or changing position. The ViewPager will keep the current page active provided the adapter implements the method {@link #getItemPosition(Object)}.

更新后ViewPager是否保持当前页存活,通过adapter实现getItemPosition方法进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*
* <p>The default implementation assumes that items will never
* change position and always returns {@link #POSITION_UNCHANGED}.
*
* @param object Object representing an item, previously returned by a call to
* {@link #instantiateItem(View, int)}.
* @return object's new position index from [0, {@link #getCount()}),
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}

POSITION_UNCHANGED不更新,POSITION_NONE更新。
所以重写getItemPosition方法,返回POSITION_NONE,可以先实现通过notifyDataSetChanged进行页面更新。

阅读全文 »