大图浏览可以说是所有App必备功能,可见其重要性,所以有必要将其独立,便于维护和复用。本文代码基于SubsamplingScaleImageView开源库实现,增加单手拖拽返回,透明度变化等效果。
浏览效果
功能实现
超大图浏览
SubsamplingScaleImageView基于BitmapRegionDecoder实现,避免OOM情况下,轻松浏览超大图,支持各种手势操作。感兴趣的请查阅其源码。
浏览Activity背景透明
123456789//manifest 设置Activity theme属性android:theme="@style/photoviewer_theme"//对应style定义<style name="photoviewer_theme" parent="Theme.AppCompat.NoActionBar"><item name="android:windowBackground">@android:color/transparent</item><item name="android:windowIsTranslucent">true</item></style>
沉浸式效果
1234567891011121314151617181920212223private void hideSystemUI() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {View decorView = getWindow().getDecorView();decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);} else {WindowManager.LayoutParams attrs = getWindow().getAttributes();attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;getWindow().setAttributes(attrs);getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);}//change navigationbar bg colorif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);getWindow().setNavigationBarColor(Color.TRANSPARENT);}}
自定义DragPhotoView拦截touch事件
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {//如果正在动画,拦截不处理。if (isAnimating()) {return true;}//判断图片是否为初始状态(第一次进入后显示的大小)if (isReady() && (firstDisplayScale == getScale())) {final int action = event.getAction();switch (action & MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_DOWN:if (DEBUG) {Log.d(TAG, "action_down = " + firstDisplayScale + ", getScale = " + getScale() + " isFirstEnterState = " + (firstDisplayScale == getScale()));}downX = event.getX();downY = event.getY();canDrag = true;break;case MotionEvent.ACTION_MOVE:if (canDrag) {final float dy = Math.abs(downY - event.getY());if (firstDisplayScale == getScale() && dy > touchSlop) {isDragging = true;translateX = event.getX() - downX;translateY = event.getY() - downY;float percent = dy / maxTranslateY;if (percent > 1.0f) {percent = 1.0f;}bgAlpha = (int) (255 * (1 - percent));bgAlpha = bgAlpha < minBgAlpha ? minBgAlpha : bgAlpha;//尽量将图片缩放比例设置小点final float p = dy / getHeight();bitmapScale = (1 - p);bitmapScale = bitmapScale < minBitmapScale ? minBitmapScale : bitmapScale;if(DEBUG) {Log.d(TAG, "action_move translateX = " + translateX + "; translateY = " + translateY + "; pointerCount = " + event.getPointerCount());}invalidate();}}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:if(DEBUG) {Log.d(TAG, "action = " + action + "; pointerCount = " + event.getPointerCount());}if (isDragging) {//最后一个手指离开屏幕时,检查y轴方向拖拽距离是否超过阀值,若超过则回调dismiss接口if (Math.abs(translateY) >= maxTranslateY && dismissListener != null) {dismissListener.onDismiss();}else{//若为超过阀值,则指定动画回到初始位置。restoreFirstEnterState();}isDragging = false;}break;case MotionEvent.ACTION_POINTER_UP:case MotionEvent.ACTION_POINTER_DOWN:if(DEBUG){Log.d(TAG, "action pointer down or up= " + action + "; pointerCount = " + event.getPointerCount());}//防止拖拽过程中多点触摸导致事件错乱。canDrag = isDragging;break;default:break;}}return isDragging;}});@Overrideprotected void onDraw(Canvas canvas) {//肯定touch move事件,不断更新以下几个变量的值来达到动画效果。createPaint();bgPaint.setAlpha(bgAlpha);canvas.drawRect(0, 0, getWidth(), getHeight(), bgPaint);canvas.translate(translateX, translateY);canvas.scale(bitmapScale, bitmapScale, getWidth() / 2, getHeight() / 2);super.onDraw(canvas);}
点击位置(进入和退出动画)
// TODO 非刚需,暂未实现。
兼容处理(18.03.03更新)
Image failed to decode using JPEG decoder
由于使用了SubsamplingScaleImageView库显示大图,内部使用的是系统BitmapRegionDecoder解码,其只支持JPG和PNG格式,而且在不同的设备和版本上表现有差异,一句话:不稳定,容易出问题。官方给出的方案Custom-decoders。
我的解决思路:监听SubsamplingScaleImageView解码错误回调,转为使用PhotoView显示图片。
源码地址
以上提及代码均在AndroidUiKit项目(安卓常用UI组件库。 总结、沉淀、封装优化;为避免重复造轮子,此项目会收集优秀的三方库,或直接引用,或修改源码;目标很明确:快速集成开发,提高效率。)
代码位置:uikit module中photoviewer包下
App图片处理库推荐
- 图片加载神器Glide
- Multi-media selector(本地图片视频选择器) AndroidUiKit项目中有推荐。