/* * Copyright (C) 2016 Peng fei Pan <sky@xiaopan.me> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.xiaopan.sketchsample.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ViewGroup; import me.xiaopan.sketch.SLogType; import me.xiaopan.sketch.SketchImageView; import me.xiaopan.sketch.SLog; import me.xiaopan.sketch.drawable.LoadingDrawable; import me.xiaopan.sketch.feature.large.LargeImageViewer; import me.xiaopan.sketch.feature.large.Tile; import me.xiaopan.sketch.request.DisplayParams; import me.xiaopan.sketch.util.SketchUtils; public class MappingView extends SketchImageView { private LargeImageViewer largeImageViewer; private Rect visibleMappingRect; private Paint visiblePaint; private Paint drawTilesPaint; private Paint realSrcRectPaint; private Paint originSrcRectPaint; private Paint loadingTilePaint; private Point drawableSize = new Point(); private Rect visibleRect = new Rect(); private GestureDetector detector; private OnSingleClickListener onSingleClickListener; public MappingView(Context context) { super(context); init(context); } public MappingView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MappingView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { visibleMappingRect = new Rect(); visiblePaint = new Paint(); visiblePaint.setColor(Color.RED); visiblePaint.setStyle(Paint.Style.STROKE); visiblePaint.setStrokeWidth(SketchUtils.dp2px(context, 1)); drawTilesPaint = new Paint(); drawTilesPaint.setColor(Color.parseColor("#88A020F0")); drawTilesPaint.setStrokeWidth(SketchUtils.dp2px(context, 1)); drawTilesPaint.setStyle(Paint.Style.STROKE); loadingTilePaint = new Paint(); loadingTilePaint.setColor(Color.parseColor("#880000CD")); loadingTilePaint.setStrokeWidth(SketchUtils.dp2px(context, 1)); loadingTilePaint.setStyle(Paint.Style.STROKE); realSrcRectPaint = new Paint(); realSrcRectPaint.setColor(Color.parseColor("#8800CD00")); realSrcRectPaint.setStrokeWidth(SketchUtils.dp2px(context, 1)); realSrcRectPaint.setStyle(Paint.Style.STROKE); originSrcRectPaint = new Paint(); originSrcRectPaint.setColor(Color.parseColor("#88FF7F24")); originSrcRectPaint.setStrokeWidth(SketchUtils.dp2px(context, 1)); originSrcRectPaint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (largeImageViewer != null && largeImageViewer.isReady()) { float widthScale = (float) largeImageViewer.getImageSize().x / getWidth(); float heightScale = (float) largeImageViewer.getImageSize().y / getHeight(); for (Tile tile : largeImageViewer.getTileList()) { if (!tile.isEmpty()) { canvas.drawRect((tile.srcRect.left + 1) / widthScale, (tile.srcRect.top + 1) / heightScale, (tile.srcRect.right - 1) / widthScale, (tile.srcRect.bottom - 1) / heightScale, drawTilesPaint); } else if (!tile.isDecodeParamEmpty()) { canvas.drawRect((tile.srcRect.left + 1) / widthScale, (tile.srcRect.top + 1) / heightScale, (tile.srcRect.right - 1) / widthScale, (tile.srcRect.bottom - 1) / heightScale, loadingTilePaint); } } Rect drawSrcRect = largeImageViewer.getDrawSrcRect(); if (!drawSrcRect.isEmpty()) { canvas.drawRect((drawSrcRect.left) / widthScale, (drawSrcRect.top) / heightScale, (drawSrcRect.right) / widthScale, (drawSrcRect.bottom) / heightScale, originSrcRectPaint); } Rect decodeSrcRect = largeImageViewer.getDecodeSrcRect(); if (!decodeSrcRect.isEmpty()) { canvas.drawRect((decodeSrcRect.left) / widthScale, (decodeSrcRect.top) / heightScale, (decodeSrcRect.right) / widthScale, (decodeSrcRect.bottom) / heightScale, realSrcRectPaint); } } if (!visibleMappingRect.isEmpty()) { canvas.drawRect(visibleMappingRect, visiblePaint); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); recover(); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); resetViewSize(); } @Override public boolean onTouchEvent(MotionEvent event) { if (detector == null) { return super.onTouchEvent(event); } detector.onTouchEvent(event); return true; } private String getImageUri() { DisplayParams displayParams = getDisplayParams(); return displayParams != null ? displayParams.info.getUri() : null; } public void update(Point newDrawableSize, Rect newVisibleRect) { if (newDrawableSize.x == 0 || newDrawableSize.y == 0 || newVisibleRect.isEmpty()) { if (SLogType.ZOOM.isEnabled()) { SLog.w(SLogType.ZOOM, "MappingView. update. drawableWidth is 0 or newVisibleRect is empty. %s. drawableSize=%s, newVisibleRect=%s", getImageUri(), newDrawableSize.toString(), newVisibleRect.toShortString()); } drawableSize.set(0, 0); visibleRect.setEmpty(); if (!visibleMappingRect.isEmpty()) { visibleMappingRect.setEmpty(); invalidate(); } return; } drawableSize.set(newDrawableSize.x, newDrawableSize.y); visibleRect.set(newVisibleRect); if (!isUsableDrawable() || getWidth() == 0 || getHeight() == 0) { if (SLogType.ZOOM.isEnabled()) { SLog.w(SLogType.ZOOM, "MappingView. update. view size is 0 or getDrawable() is null. %s", getImageUri()); } if (!visibleMappingRect.isEmpty()) { visibleMappingRect.setEmpty(); invalidate(); } return; } if (resetViewSize()) { return; } resetVisibleMappingRect(); invalidate(); } public void tileChanged(LargeImageViewer largeImageViewer) { this.largeImageViewer = largeImageViewer; invalidate(); } public void setOnSingleClickListener(OnSingleClickListener onSingleClickListener) { this.onSingleClickListener = onSingleClickListener; setClickable(onSingleClickListener != null); if (detector == null) { detector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return MappingView.this.onSingleClickListener.onSingleClick(e.getX(), e.getY()); } }); } } private void recover() { if (!visibleRect.isEmpty()) { update(drawableSize, visibleRect); } } public boolean isUsableDrawable() { Drawable drawable = getDrawable(); return drawable != null && !(drawable instanceof LoadingDrawable); } private boolean resetViewSize() { Drawable drawable = getDrawable(); if (drawable == null) { return true; } final int drawableWidth = drawable.getIntrinsicWidth(); final int drawableHeight = drawable.getIntrinsicHeight(); int maxWidth; int maxHeight; if ((float) Math.max(drawableWidth, drawableHeight) / Math.min(drawableWidth, drawableHeight) >= 4) { maxWidth = Math.round(getResources().getDisplayMetrics().widthPixels / 2f); maxHeight = Math.round(getResources().getDisplayMetrics().heightPixels / 2f); } else { maxWidth = Math.round(getResources().getDisplayMetrics().widthPixels / 4f); maxHeight = Math.round(getResources().getDisplayMetrics().heightPixels / 4f); } int newViewWidth; int newViewHeight; if (drawableWidth > maxWidth || drawableHeight > maxHeight) { float finalScale = Math.min((float) maxWidth / drawableWidth, (float) maxHeight / drawableHeight); newViewWidth = Math.round(drawableWidth * finalScale); newViewHeight = Math.round(drawableHeight * finalScale); } else { newViewWidth = drawableWidth; newViewHeight = drawableHeight; } ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (newViewWidth != layoutParams.width || newViewHeight != layoutParams.height) { layoutParams.width = newViewWidth; layoutParams.height = newViewHeight; setLayoutParams(layoutParams); return true; } else { return false; } } private void resetVisibleMappingRect() { int selfWidth = getWidth(); int selfHeight = getHeight(); final float widthScale = (float) selfWidth / drawableSize.x; final float heightScale = (float) selfHeight / drawableSize.y; this.visibleMappingRect.set( Math.round(visibleRect.left * widthScale), Math.round(visibleRect.top * heightScale), Math.round(visibleRect.right * widthScale), Math.round(visibleRect.bottom * heightScale)); } public interface OnSingleClickListener { boolean onSingleClick(float x, float y); } }