UI calibration is one of the latest features comes into Hurdle. With this functionality, UEDs and developers are able to check whether the UI implementation is aligned with design spec. Color-picker and magnifier is one of the feature inside.
Obtain bitmap from Activity
The content to show inside MagnifierView is a crop from background Activity bitmap. To obtain a bitmap of the Activity:
Before implementing onDraw(), there are something to be prepared. First we have a content-bitmap and a mask-bitmap, both with the same size of the MagnifierView, say 130*130dp. The mask-bitmap is from a bitmap drawable which is a white-colored circle with transparent background.
// width and height is MagnifierView dimension// the target bitmap which will be shown inside MagnifierViewBitmapmTargetBitmap=Bitmap.createBitmap(width,height,BitmapConfig.ARGB_8888);// mCanvasTarget will draw into mTargetBitmapCanvasmCanvasTarget=newCanvas(mTargetBitmap);// the activity-bitmap will be cropped and drawn into mContentBitmap firstBitmapmContentBitmap=Bitmap.createBitmap(width,height,BitmapConfig.ARGB_8888);CanvasmCanvasContent=newCanvas(mContentBitmap);// the paint object to mask the content-bitmap with mask-bitmapPaintmPaintMask=newPaint(Paint.ANTI_ALIAS_FLAG);mPaintMask.setXferMode(newPorterDuffXfermode(Mode.SRC_IN));// magnify matrixMatrixmMatrix=newMatrix();mMatrix.setScale(1.2f,1.2f);
Draw the magnifier
Whenever the magnifier is moved, calculate the part of activity-bitmap which should be shown inside, copy that area into content-bitmap, as the content-bitmap is much smaller than activity-bitmap, it is cropped. Then mask the content-bitmap against the mask-bitmap, which translate the shape of content-bitmap from rectanglar to circle.
@OverridepublicbooleanonTouchEvent(MotionEventevent){floatrawX=event.getRawX();floatrawY=event.getRawY();if(event.getAction()==MotionEvent.ACTION_MOVE{// avoid obstruction by the finger, make the magnifier a little bit above the touch pointintx=(int)(rawX-mContentBitmap.getWidth()/2);inty=(int)(rawY-mContentBitmap.getHeight());if(x<0)x=0;if(y<0)y=0;// update the content of the magnifierupdateMagnifierContent(x,y);// update the positon of the magnifier with WindowManagerupdateMagnifierPosition(x,y);}}
Somebody may point out that rawY should be substract by the height of system notification bar. But in fact, the Activity window is actually drawn full-screen, the notification bar is an overlay at the top. The Activity window just leaves the area obstructed as transparent.
/**
* @param bmpLtX: the top-left location X of the bitmap to be drawn
* @param bmpLtY: the top-left location Y of the bitmap to be drawn
*/privatevoidupdateMagnifierContent(intbmpLtX,intbmpLtY){// draw the portion of activity-bitmap into mContentBitmap, as a intermediate buffermContentBitmap.eraseColor(0);mCanvasContent.save();// note: negative values usedmCanvasContent.translate(-bmpLtX,-bmpLtY);mCanvasContent.drawBitmap(mActivityBitmap,0,0,mPaint);mCanvasContent.restore();mTargetBitmap.eraseColor(0);// the drawble of mask-bitmapDrawablemaskDrawable=getMaskDrawable();maskDrawable.draw(mCanvasTarget);mCanvasTarget.save();// mask mContentBitmap with mask-bitmap, by PorterDuffXfermode(mPaintMask),// to make it a circle,// with mMatrix, the content-bitmap is magnified by 20%mCanvasTarget.drawBitmap(mContentBitmap,mMatrix,mPaintMask);mCanvasTarget.restore();// draw other decorations ...// will trigger onDraw()invalidate();}@OverridepublicvoidonDraw(Canvascanvas){canvas.drawBitmap(mTargetBitmap,0,0,null);}
Please note that the parameters passed to Canvas.translate() are (-bmpLtX, -bmpLtY), instead of (bmpLtX, bmpLtY). I was quite confusing about this at the begining. Translation moves the base point (0, 0) of the Canvas to (-bmpLtX, -bmpLtY), say (-150, -200). When the activity-bitmap is then drawn to the canvas, the base point of the bitmap is actually drawn at view coordinator (-150, -200), which is out of the screen and will not displayed. So the point of bitmap (150, 200) is now drawn at view coordinator (0, 0), that exactly what I want.
Update window location
The MagnifierView is show atop of any Activity. It's manipulated directly by WindowManager.
privatevoidshowMagnifier(){WindowManager.LayoutParamsparams=newWindowManager.LayoutParams();params.type=LayoutParams.SYSTEM_ALERT-1;// FLAG_NOT_TOUCH_MODAL ensures that the magnifier doesn't caputre all the touch events outside the viewparams.flags=FLAG_ALT_FOCUSABLE_IM|FLAG_HARDWARE_ACCELERATED|FLAG_NOT_TOUCH_MODAL;params.format=PixelFormat.TRANSLUCENT;params.width=WRAP_CONTENT;params.height=WRAP_CONTENT;params.gravity=LEFT|TOP;// only for LEFT-TOP, params.x, y will take effectmWindowManager.addView(overlay,params);}privatevoidupdateMagnifierPosition(intx,inty){WindowManager.LayoutParamslp=(WindowManager.LayoutParams)this.getLayoutParams();lp.x=x;lp.y=y;mWindowManager.updateViewLayout(this,lp);}