Android - Pinch-zoomable Android frame layout - java

I used this to perform pinch zoom and drag.
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
}
The problem that I am having with this is that when I zoom in, then drag, it snaps to the middle of the screen. I think the problem lies at:
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
I am not sure how to fix this, can anyone please help me?

I fixed it by changing onScale to:
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "mode:" + this.mode + ", onScale:" + scaleFactor);
if (this.lastScaleFactor == 0.0f || Math.signum(scaleFactor) == Math.signum(this.lastScaleFactor)) {
this.scale *= scaleFactor;
this.scale = Math.max(MIN_ZOOM, Math.min(this.scale, MAX_ZOOM));
this.lastScaleFactor = scaleFactor;
} else {
this.lastScaleFactor = 0.0f;
}
if (this.mSurfaceView != null) {
int orgWidth = getWidth();
int _width = (int) (((float) orgWidth) * this.scale);
int _height = (int) (((float) getHeight()) * this.scale);
LogUtil.b(new StringBuilder(String.valueOf(_width)).append(", ").append(_height).toString());
LayoutParams params = (LayoutParams) this.mSurfaceView.getLayoutParams();
params.height = _height;
params.width = _width;
this.mSurfaceView.setLayoutParams(params);
child().setScaleX(this.scale);
child().setScaleY(this.scale);
}
return true;
}

Related

Multiple Sticker Erase Issue

I try to make image editor application. in this application i try to put multiple sticker on image that sticker can move , zoom , rotate, and also selected sticker can erase . can anyone help me ? here is code that i done with sticker move,rotate,zoom but i cant erase selected sticker .
public class MainActivity extends AppCompatActivity {
Bitmap originalBitmap,originalBitmap1;
RelativeLayout rlImageViewContainer;
private ImageView ivRedo;
private ImageView ivUndo;
Button btn_erase;
private ArrayList<Path> paths;
private ArrayList<Path> redoPaths;
Paint destPaint = new Paint();
Path destPath = new Path();
Bitmap destBitmap;
Canvas destCanvas = new Canvas();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);
originalBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.b);
initView();
}
private void initView() {
ivUndo = (ImageView) findViewById(R.id.iv_undo);
ivRedo = (ImageView) findViewById(R.id.iv_redo);
rlImageViewContainer = findViewById(R.id.rl_image_view_container);
final PhotoSortrView photoSortrView = new PhotoSortrView(this);
rlImageViewContainer.addView(photoSortrView,0);
photoSortrView.setBackgroundColor(getResources().getColor(R.color.tran));
photoSortrView.addImages(this, originalBitmap);
photoSortrView.addImages(this, originalBitmap1);
ivUndo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// undo();
}
});
ivRedo.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// redo();
}
});
btn_erase = (Button) findViewById(R.id.btn_erase);
btn_erase.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (photoSortrView.isErase) {
btn_erase.setBackgroundColor(Color.RED);
photoSortrView.isErase = false;
photoSortrView.isMove = true;
} else {
btn_erase.setBackgroundColor(Color.GREEN);
photoSortrView.isErase = true;
photoSortrView.isMove = false;
}
}
});
}
}
PhotoSortrView.java
public class PhotoSortrView extends View implements MultiTouchController.MultiTouchObjectCanvas<MultiTouchEntity> {
private static final String TAG = "PhotoSortr ####### ";
// private static final int[] IMAGES = { R.drawable.m74hubble };
private Path drawPath = new Path();
// drawing and canvas paint
private Paint drawPaint = new Paint(), canvasPaint = new Paint();
// initial color
private int paintColor = Color.TRANSPARENT;
// canvas
private Canvas drawCanvas = new Canvas();
// canvas bitmap
private Bitmap canvasBitmap;
private ArrayList<MultiTouchEntity> imageIDs = new ArrayList<MultiTouchEntity>();
// --
private MultiTouchController<MultiTouchEntity> multiTouchController = new MultiTouchController<MultiTouchEntity>(this);
// --
private MultiTouchController.PointInfo currTouchPoint = new MultiTouchController.PointInfo();
private static final int UI_MODE_ROTATE = 1, UI_MODE_ANISOTROPIC_SCALE = 2;
private int mUIMode = UI_MODE_ROTATE;
// --
private static final float SCREEN_MARGIN = 100;
private int displayWidth, displayHeight;
public boolean isErase = false;
public boolean isMove = true;
Paint destPaint = new Paint();
Path destPath = new Path();
Bitmap destBitmap;
Canvas destCanvas = new Canvas();
int height,width;
// ---------------------------------------------------------------------------------------------------
public PhotoSortrView(Context context) {
this(context, null);
setupPaint();
init(context);
}
private void setupPaint() {
destPaint.setAlpha(0);
destPaint.setAntiAlias(true);
destPaint.setStyle(Paint.Style.STROKE);
destPaint.setStrokeJoin(Paint.Join.ROUND);
destPaint.setStrokeCap(Paint.Cap.ROUND);
destPaint.setStrokeWidth(20);
destPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}
public PhotoSortrView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
setupDrawing();
}
public PhotoSortrView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
Log.e(TAG, "init: " );
Resources res = context.getResources();
setBackgroundColor(Color.TRANSPARENT);
DisplayMetrics metrics = res.getDisplayMetrics();
this.displayWidth = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math
.max(metrics.widthPixels, metrics.heightPixels) : Math.min(
metrics.widthPixels, metrics.heightPixels);
this.displayHeight = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math
.min(metrics.widthPixels, metrics.heightPixels) : Math.max(
metrics.widthPixels, metrics.heightPixels);
}
public void addImages(Context context, Bitmap resourceId) {
Log.e(TAG, "addImages: " );
// setupDrawing();
Resources res = context.getResources();
imageIDs.add(new ImageEntity(resourceId, res));
float cx = SCREEN_MARGIN + (float)
(Math.random() * (displayWidth - 2 * SCREEN_MARGIN));
float cy = SCREEN_MARGIN + (float)
(Math.random() * (displayHeight - 2 * SCREEN_MARGIN));
imageIDs.get(imageIDs.size() - 1).load(context, cx, cy);
invalidate();
}
public void removeAllImages() {
imageIDs.removeAll(imageIDs);
invalidate();
}
public void removeImage() {
if (imageIDs.size() > 0) {
imageIDs.remove(imageIDs.size() - 1);
}
invalidate();
}
public int getCountImage() {
return imageIDs.size();
}
// ---------------------------------------------------------------------------------------------------
#Override
protected void onDraw(Canvas canvas) {
Log.e(TAG, "onDraw: " );
if (isErase){
Log.e(TAG, "onDraw: isErase" );
if (destBitmap != null){
destCanvas.drawPath(destPath,destPaint);
canvas.drawBitmap(destBitmap,0,0,null);
}
}
else if (isMove){
height = imageIDs.get(0).getHeight();
width = imageIDs.get(0).getWidth();
Log.e(TAG, "onDraw: isMove");
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath,drawPaint);
int n = imageIDs.size();
for (int i = 0; i < n; i++)
imageIDs.get(i).draw(canvas);
}
super.onDraw(canvas);
}
// ---------------------------------------------------------------------------------------------------
public void trackballClicked() {
mUIMode = (mUIMode + 1) % 3;
invalidate();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
//collegeActivity.set
int action = event.getAction();
Log.e(TAG, "onTouchEvent: " );
float touchX = event.getX();
float touchY = event.getY();
// respond to down, move and up events
if (isErase){
Log.e(TAG, "onTouchEvent: isErase" );
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
destPath.moveTo(touchX,touchY);
break;
case MotionEvent.ACTION_MOVE:
destPath.lineTo(touchX,touchY);
break;
}
} else if (isMove) {
Log.e(TAG, "onTouchEvent: isMove" );
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
return multiTouchController.onTouchEvent(event);
}
invalidate();
return true;
}
private void setupDrawing() {
Log.e(TAG, "setupDrawing: " );
// prepare for drawing and setup paint stroke properties
drawPath = new Path();
drawPaint.setAlpha(0);
drawPaint.setColor(0);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(10);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
// size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w > 0 && h > 0) {
Log.e(TAG, "onSizeChanged: " );
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
}
// update color
public void setColor(String newColor) {
Log.e(TAG, "setColor: " );
invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
}
public MultiTouchEntity getDraggableObjectAtPoint(MultiTouchController.PointInfo pt) {
float x = pt.getX(), y = pt.getY();
int n = imageIDs.size();
for (int i = n - 1; i >= 0; i--) {
ImageEntity im = (ImageEntity) imageIDs.get(i);
if (im.containsPoint(x, y))
return im;
}
return null;
}
public void selectObject(MultiTouchEntity img, MultiTouchController.PointInfo touchPoint) {
currTouchPoint.set(touchPoint);
if (img != null) {
// Move image to the top of the stack when selected
drawPaint.setColor(Color.TRANSPARENT);
imageIDs.remove(img);
imageIDs.add(img);
destCanvas = new Canvas();
destBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
destCanvas.setBitmap(destBitmap);
destCanvas.drawBitmap(img.getBitmap(),0,0,null);
} else {
// Called with img == null when drag stops.
}
invalidate();
}
public void getPositionAndScale(MultiTouchEntity img, MultiTouchController.PositionAndScale objPosAndScaleOut) {
// requires averaging the two scale factors)
objPosAndScaleOut.set(img.getCenterX(), img.getCenterY(),
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) == 0,
(img.getScaleX() + img.getScaleY()) / 2,
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0, img.getScaleX(),
img.getScaleY(), (mUIMode & UI_MODE_ROTATE) != 0,
img.getAngle());
}
/**
* Set the position and scale of the dragged/stretched image.
*/
public boolean setPositionAndScale(MultiTouchEntity img,
MultiTouchController.PositionAndScale newImgPosAndScale, MultiTouchController.PointInfo touchPoint) {
currTouchPoint.set(touchPoint);
boolean ok = img.setPos(newImgPosAndScale);
if (ok)
invalidate();
return ok;
}
public boolean pointInObjectGrabArea(MultiTouchController.PointInfo pt, MultiTouchEntity img) {
return false;
}
}

Children match_parent ignoring parent in custom view with viewdraghelper

I have been studying custom views recently and trying to make one specific example work out from a tutorial located here: http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/
I will add the relevant code to prevent information loss from dead link. The author of the tutorial explains how to create a ViewDragHelper similar to YouTube's android app player behavior and these are the codes he provided
activity_main.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="list"
/>
<com.example.vdh.YoutubeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/youtubeLayout"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="#+id/viewHeader"
android:layout_width="match_parent"
android:layout_height="128dp"
android:fontFamily="sans-serif-thin"
android:textSize="25sp"
android:tag="text"
android:gravity="center"
android:textColor="#android:color/white"
android:background="#AD78CC"/>
<TextView
android:id="#+id/viewDesc"
android:tag="desc"
android:textSize="35sp"
android:gravity="center"
android:text="Loreum Loreum"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF00FF"/>
</com.example.vdh.YoutubeLayout>
YoutubeLayout.java
public class YoutubeLayout extends ViewGroup {
private final ViewDragHelper mDragHelper;
private View mHeaderView;
private View mDescView;
private float mInitialMotionX;
private float mInitialMotionY;
private int mDragRange;
private int mTop;
private float mDragOffset;
public YoutubeLayout(Context context) {
this(context, null);
}
public YoutubeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#Override
protected void onFinishInflate() {
mHeaderView = findViewById(R.id.viewHeader);
mDescView = findViewById(R.id.viewDesc);
}
public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
}
public void maximize() {
smoothSlideTo(0f);
}
boolean smoothSlideTo(float slideOffset) {
final int topBound = getPaddingTop();
int y = (int) (topBound + slideOffset * mDragRange);
if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
#Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mHeaderView;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mTop = top;
mDragOffset = (float) top / mDragRange;
mHeaderView.setPivotX(mHeaderView.getWidth());
mHeaderView.setPivotY(mHeaderView.getHeight());
mHeaderView.setScaleX(1 - mDragOffset / 2);
mHeaderView.setScaleY(1 - mDragOffset / 2);
mDescView.setAlpha(1 - mDragOffset);
requestLayout();
}
#Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int top = getPaddingTop();
if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
top += mDragRange;
}
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
}
#Override
public int getViewVerticalDragRange(View child) {
return mDragRange;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
}
#Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (( action != MotionEvent.ACTION_DOWN)) {
mDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
final float x = ev.getX();
final float y = ev.getY();
boolean interceptTap = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
break;
}
case MotionEvent.ACTION_MOVE: {
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
final int slop = mDragHelper.getTouchSlop();
if (ady > slop && adx > ady) {
mDragHelper.cancel();
return false;
}
}
}
return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
mDragHelper.processTouchEvent(ev);
final int action = ev.getAction();
final float x = ev.getX();
final float y = ev.getY();
boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
break;
}
case MotionEvent.ACTION_UP: {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
if (mDragOffset == 0) {
smoothSlideTo(1f);
} else {
smoothSlideTo(0f);
}
}
break;
}
}
return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);
}
private boolean isViewHit(View view, int x, int y) {
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mDragRange = getHeight() - mHeaderView.getHeight();
mHeaderView.layout(
0,
mTop,
r,
mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(
0,
mTop + mHeaderView.getMeasuredHeight(),
r,
mTop + b);
}
}
The author notes that onLayoutand onMeasure are badly written and I assume these (or one of them) might be the cause of a problem with one of the children.
For my objective, I replaced the mDescView with a Framelayout view containing the respective TextView. Both of them have their height set to match_parent and the parent (mDescView) does set its height correctly, but its children (TextView inside mDescView) ignore the parent height and stretch to fit their height equal to the screen height (or the custom view height, can't tell the difference). This is a problem because the mDescView children will never adjust their height correctly according to the parent through match_parent and I have been looking for a solution for days, but none was found and through research I couldn't find a reason for why this was happening.
This is the result of this problem, notice how the TextView height is bigger than its parent mDescView despite both having their height set to match_parent
So my problem is, how can I get the child (children) of that parent (or any parents) to match their parents' height like they should.
As an additional request, if possible, can anyone explain why the author thinks some of his methods are not the best/right ones and how they should be done correctly/better instead.
I have finally corrected this problem, although I am still unaware if this is the right way to do this. My solution was to set both views' height during onLayout
I have also set this to only run on the first time this method is called, so the specific statement only runs once and only during the first time (when firstRun is true)
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mDragRange = getHeight() - mHeaderView.getHeight();
if (firstRun) {
firstRun = false;
mDescView.getLayoutParams().height = getHeight() - mHeaderView.getMeasuredHeight();
}
mHeaderView.layout(
0,
mTop,
r,
mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(
0,
mTop + mHeaderView.getMeasuredHeight(),
r,
mTop + b);
}

How to maintain the focus point in a Pan/Zoom Android Layout while zooming?

I have a custom Layout that extends FrameLayout and supports almost everything I need. Scrolling and zooming a child view or layout works well. But one thing I am having trouble with is maintaining the focus point while zooming. The center point between the pinching fingers moves off screen, but I would like it to work like the Google Maps app where the focus(center) point is maintained.
Here's the code.
public class BetterZoomLayout extends FrameLayout
{
// State objects and values related to gesture tracking.
private ScaleGestureDetector mScaleGestureDetector;
private GestureDetectorCompat mGestureDetector;
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// How much to translate the canvas
private float distanceX = 0f;
private float distanceY = 0f;
public BetterZoomLayout(Context context)
{
super(context);
init(context);
}
public BetterZoomLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public BetterZoomLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
private void init(Context context)
{
// Sets up interactions
mScaleGestureDetector = new ScaleGestureDetector(context, mScaleGestureListener);
mGestureDetector = new GestureDetectorCompat(context, mGestureListener);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
retVal = mGestureDetector.onTouchEvent(event) || retVal;
applyScaleAndTranslation();
return retVal || super.onTouchEvent(event);
}
/**
* The scale listener, used for handling multi-finger scale gestures.
*/
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
= new ScaleGestureDetector.SimpleOnScaleGestureListener()
{
/**
* This is the active focal point in terms of the viewport. Could be a local
* variable but kept here to minimize per-frame allocations.
*/
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector)
{
lastScaleFactor = 0f;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector)
{
float scaleFactor = scaleGestureDetector.getScaleFactor();
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor)))
{
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
}
else
{
lastScaleFactor = 0;
}
return true;
}
};
/**
* The gesture listener, used for handling simple gestures such as double touches, scrolls,
* and flings.
*/
private final GestureDetector.SimpleOnGestureListener mGestureListener
= new GestureDetector.SimpleOnGestureListener()
{
#Override
public boolean onDown(MotionEvent e)
{
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY)
{
setupTranslation(dX, dY);
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
//fling((int) -velocityX, (int) -velocityY);
return true;
}
};
private void setupTranslation(float dX, float dY)
{
distanceX = -1 * dX + distanceX;
distanceY = -1 * dY + distanceY;
getParent().requestDisallowInterceptTouchEvent(true);
Rect viewableRect = new Rect();
BetterZoomLayout.this.getDrawingRect(viewableRect);
float offscreenWidth = child().getWidth() - (viewableRect.right - viewableRect.left);
float offscreenHeight = child().getHeight() - (viewableRect.bottom - viewableRect.top);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
distanceX = Math.min(Math.max(distanceX, -(maxDx + offscreenWidth)), maxDx);
distanceY = Math.min(Math.max(distanceY, -(maxDy + offscreenHeight)), maxDy);
}
private void applyScaleAndTranslation()
{
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(distanceX);
child().setTranslationY(distanceY);
}
private View child()
{
return getChildAt(0);
}
}

Change Custom View Width And Height In Android

In my android app, I have a crop image. So, I programming a custom view as my crop box. I can move the crop box. But my problem is how can I drag border of the crop box and change width and height of it. How can I do it?
Attr Class:
public class Attr {
public static final float CROP_BOX_START_X = 5;
public static final float CROP_BOX_START_Y = 5;
public static final float CROP_BOX_END_X = 305;
public static final float CROP_BOX_END_Y = 105;
}
CropBox Class:
public class CropBox extends View {
private Paint paint = new Paint();
public CropBox(Context context) {
super(context);
}
public CropBox(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
#Override
public void onDraw(Canvas canvas) {
float[][] circleXY = {
{Attr.CROP_BOX_START_X, Attr.CROP_BOX_START_Y},
{(Attr.CROP_BOX_START_X + Attr.CROP_BOX_END_X) / 2, Attr.CROP_BOX_START_Y},
{Attr.CROP_BOX_END_X, Attr.CROP_BOX_START_Y},
{Attr.CROP_BOX_START_X, Attr.CROP_BOX_END_Y},
{(Attr.CROP_BOX_START_X + Attr.CROP_BOX_END_X) / 2, Attr.CROP_BOX_END_Y},
{Attr.CROP_BOX_END_X, Attr.CROP_BOX_END_Y},
{Attr.CROP_BOX_START_X, (Attr.CROP_BOX_START_Y + Attr.CROP_BOX_END_Y) / 2},
{Attr.CROP_BOX_END_X, (Attr.CROP_BOX_START_Y + Attr.CROP_BOX_END_Y) / 2}
};
float[][] lineXY = {
{Attr.CROP_BOX_START_X, Attr.CROP_BOX_START_Y, Attr.CROP_BOX_END_X, Attr.CROP_BOX_START_Y},
{Attr.CROP_BOX_START_X, Attr.CROP_BOX_END_Y, Attr.CROP_BOX_END_X, Attr.CROP_BOX_END_Y},
{Attr.CROP_BOX_START_X, Attr.CROP_BOX_START_Y, Attr.CROP_BOX_START_X, Attr.CROP_BOX_END_Y},
{Attr.CROP_BOX_END_X, Attr.CROP_BOX_START_Y, Attr.CROP_BOX_END_X, Attr.CROP_BOX_END_Y}
};
paint.setColor(Color.CYAN);
paint.setStrokeWidth(1);
for(int i = 0 ; i < circleXY.length ; i++)
canvas.drawCircle(circleXY[i][0], circleXY[i][1], 5, paint);
paint.setStrokeWidth(2);
for(int i = 0 ; i < lineXY.length ; i++)
canvas.drawLine(lineXY[i][0], lineXY[i][2], lineXY[i][2], lineXY[i][3], paint);
}
}
CropTestActivity Class:
public class CropTestActivity extends Activity {
private ImageView imageView;
private CropBox cropBox;
private RelativeLayout relativeLayout;
private RelativeLayout.LayoutParams layoutParams;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crop_test_layout);
imageView = (ImageView)findViewById(R.id.android_image);
cropBox = new CropBox(this);
relativeLayout = (RelativeLayout)findViewById(R.id.crop_test_layout);
layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT);
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
layoutParams.leftMargin = imageView.getWidth() / 2 - (int)((Attr.CROP_BOX_START_X + Attr.CROP_BOX_END_X) / 2) + imageView.getLeft();
layoutParams.topMargin = imageView.getHeight() / 2 - (int)((Attr.CROP_BOX_START_Y + Attr.CROP_BOX_END_Y) / 2) + imageView.getTop();
}
});
relativeLayout.addView(cropBox, layoutParams);
cropBox.setOnTouchListener(new Crop(imageView));
}
}
Crop Class:
public class Crop implements OnTouchListener {
private static final int NONE = 0;
private static final int BOX_DRAG = 1;
private static final int BORDER_DRAG = 2;
private int mode = NONE;
private float cropBoxStartX = Attr.CROP_BOX_START_X;
private float cropBoxStartY = Attr.CROP_BOX_START_Y;
private float cropBoxEndX = Attr.CROP_BOX_END_X;
private float cropBoxEndY = Attr.CROP_BOX_END_Y;
private ImageView imageView;
private PointF start = new PointF();
public Crop(ImageView imageView) {
this.imageView = imageView;
}
public boolean onTouch(View view, MotionEvent event) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams();
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY());
if(event.getX() > cropBoxStartX && event.getX() < cropBoxEndX && event.getY() > cropBoxStartY && event.getY() < cropBoxEndY)
mode = BOX_DRAG;
else if(event.getX() == cropBoxStartX || event.getX() == cropBoxEndX || event.getY() == cropBoxStartY || event.getY() == cropBoxEndY)
mode = BORDER_DRAG;
else
mode = NONE;
break;
case MotionEvent.ACTION_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if(mode == BOX_DRAG) {
layoutParams.leftMargin = (int)event.getX() - (int)start.x + view.getLeft();
layoutParams.topMargin = (int)event.getY() - (int)start.y + view.getTop();
while(layoutParams.topMargin + 5 < imageView.getTop())
layoutParams.topMargin++;
while(layoutParams.leftMargin + (cropBoxEndX - cropBoxStartX + 5) > imageView.getRight())
layoutParams.leftMargin--;
while(layoutParams.topMargin + (cropBoxEndY - cropBoxStartY + 5) > imageView.getBottom())
layoutParams.topMargin--;
while(layoutParams.leftMargin + 5 < imageView.getLeft())
layoutParams.leftMargin++;
}
else if(mode == BORDER_DRAG) {
}
break;
}
view.setLayoutParams(layoutParams);
return true;
}
}
Layout XML:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/crop_test_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="#+id/android_image"
android:src="#drawable/android"
android:layout_width="fill_parent"
android:layout_height="300dp"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_gravity="center"
android:scaleType="fitXY"
android:contentDescription="#string/android_image_description" >
</ImageView>
</RelativeLayout>
Before Resize:
After Resize:
Thanks for your help.
Following is the solution,
Modified onCreate from Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crop_test_layout);
imageView = (ImageView)findViewById(R.id.android_image);
cropBox = new CropBox(this, imageView);
relativeLayout = (RelativeLayout)findViewById(R.id.crop_test_layout);
relativeLayout.addView(cropBox);
}
Modified CropBox class:
public class CropBox extends View {
private static final int CROP_BOX_START_X = 5;
private static final int CROP_BOX_START_Y = 5;
private static final int CROP_BOX_END_X = 305;
private static final int CROP_BOX_END_Y = 105;
private static final int DRAG_SQUARE = 75;
public ImageView mImageView;
boolean mIsFirstClick = false;
private Paint paint = new Paint();
private Rect mRect;
public CropBox(Context context, ImageView aBaseView) {
super(context);
mImageView = aBaseView;
mRect = new Rect(CROP_BOX_START_X, CROP_BOX_START_Y, CROP_BOX_END_X, CROP_BOX_END_Y);
setOnTouchListener(new Crop());
}
public CropBox(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
#Override
public void onDraw(Canvas canvas) {
paint.setStrokeWidth(2);
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(mRect, paint);
canvas.drawLine(mRect.right-DRAG_SQUARE, mRect.bottom-DRAG_SQUARE,
mRect.right, mRect.bottom-DRAG_SQUARE, paint);
canvas.drawLine(mRect.right-DRAG_SQUARE, mRect.bottom-DRAG_SQUARE,
mRect.right-DRAG_SQUARE, mRect.bottom, paint);
}
class Crop implements OnTouchListener {
private static final int NONE = 0;
private static final int BOX_DRAG = 1;
private static final int BORDER_DRAG = 2;
private int mode = NONE;
private PointF start = new PointF();
public boolean onTouch(View view, MotionEvent event) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams();
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY());
if((event.getX() <= mRect.right && event.getX() >=(mRect.right - DRAG_SQUARE))
&& (event.getY() >= mRect.top && event.getY() >=(mRect.bottom - DRAG_SQUARE))){
mode = BORDER_DRAG;
mIsFirstClick = false;
}
else if(mRect.contains((int)event.getX(), (int)event.getY())) {
mode = BOX_DRAG;
if (mIsFirstClick){
mRect = new Rect(CROP_BOX_START_X, CROP_BOX_START_Y,
CROP_BOX_END_X, CROP_BOX_END_Y);
mIsFirstClick = false;
} else {
mIsFirstClick = true;
}
}
else{
mode = NONE;
mIsFirstClick = true;
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
mIsFirstClick = false;
if(mode == BOX_DRAG) {
layoutParams.leftMargin = (int)event.getX() - (int)start.x + view.getLeft();
layoutParams.topMargin = (int)event.getY() - (int)start.y + view.getTop();
}
else if(mode == BORDER_DRAG) {
if (event.getX() > view.getLeft() && event.getY() > view.getTop()){
mRect.right = (int) event.getX();
mRect.bottom = (int) event.getY();
}
}
while(layoutParams.topMargin + 5 < mImageView.getTop())
layoutParams.topMargin++;
while(layoutParams.leftMargin + mRect.right > mImageView.getRight())
layoutParams.leftMargin--;
while(layoutParams.topMargin + mRect.bottom > mImageView.getBottom())
layoutParams.topMargin--;
while(layoutParams.leftMargin + 5 < mImageView.getLeft())
layoutParams.leftMargin++;
break;
}
view.setLayoutParams(layoutParams);
invalidate();
return true;
}
}
}
Some points I would like to mention.
Merged Attr and Crop in CropBox
No need of creating a rectangle from lines. You can use Rect.
Never initialize an array/object in Draw method
Added a feature: if double touched on rectangle it returns to original position
There might be some hitches about the restricting the rect in imageview. I am sure you can fix those... :)
Other than this there is another interesting way using scaling of canvas Image in Canvas with touch events
Use that class instead of Cropbox and try it.
Hope it helps..

Get Image Top and Left After Reposition In Android

I programming an android app that can drag image. Top and left of image in default mode is 0, 0. My question is how can I get top and left of visible image after drag.
Touch class:
public class Touch implements OnTouchListener {
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private static final float MIN_ZOOM = 1f;
private static final float MAX_ZOOM = 5f;
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
private PointF start = new PointF();
private PointF mid = new PointF();
private float[] values = new float[9];
private int mode = NONE;
private float dx;
private float dy;
private float matrixX = 0;
private float matrixY = 0;
private float width = 0;
private float height = 0;
private float oldDistance = 1f;
public boolean onTouch(View view, MotionEvent event) {
ImageView imageView = (ImageView)view;
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDistance = spacing(event);
if(oldDistance > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if(mode == DRAG) {
matrix.set(savedMatrix);
matrix.getValues(values);
matrixX = values[Matrix.MTRANS_X];
matrixY = values[Matrix.MTRANS_Y];
width = values[Matrix.MSCALE_X] * imageView.getDrawable().getIntrinsicWidth();
height = values[Matrix.MSCALE_Y] * imageView.getDrawable().getIntrinsicHeight();
dx = event.getX() - start.x;
dy = event.getY() - start.y;
if(matrixY + dy > 0)
while(matrixY + dy > 0)
dy--;
if(matrixX + dx + width < imageView.getWidth())
while(matrixX + dx + width < imageView.getWidth())
dx++;
if(matrixY + dy + height < imageView.getHeight())
while(matrixY + dy + height < imageView.getHeight())
dy++;
if(matrixX + dx > 0)
while(matrixX + dx > 0)
dx--;
matrix.postTranslate(dx, dy);
}
else if(mode == ZOOM) {
float newDistance = spacing(event);
if(newDistance > 10f) {
matrix.set(savedMatrix);
float scale = newDistance / oldDistance;
matrix.getValues(values);
float currentScale = values[Matrix.MSCALE_X];
if(scale * currentScale > MAX_ZOOM)
scale = MAX_ZOOM / currentScale;
else if (scale * currentScale < MIN_ZOOM)
scale = MIN_ZOOM / currentScale;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
imageView.setImageMatrix(matrix);
return true;
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
point.set((event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2);
}
}
Activity class:
public class RepositionTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reposition_test_layout);
imageView = (ImageView)findViewById(R.id.android_image);
imageView.setOnTouchListener(new Touch());
}
}
Layout xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/reposition_test_layout"
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="#+id/android_image"
android:src="#drawable/android"
android:layout_width="300dp"
android:layout_height="300dp"
android:scaleType="matrix"
android:contentDescription="#string/android_image_description" >
</ImageView>
</LinearLayout>
Thanks for your help.
I finally solve the problem. When the user reposition the image, then we can save the matrix information in a file or db.
Activity class:
public class RepositionTestActivity extends Activity {
private ImageView imageView;
private Button button;
private SQLiteDatabase db;
private Cursor cursor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reposition_test_layout);
imageView = (ImageView)findViewById(R.id.android_image);
button = (Button)findViewById(R.id.reposition_button);
ViewTreeObserver viewTreeObserver = imageView.getViewTreeObserver();
if(viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
db = openOrCreateDatabase("DB_DATING", MODE_PRIVATE, null);
db.execSQL("" +
"CREATE TABLE IF NOT EXISTS tbl_images (" +
" id INTEGER PRIMARY KEY AUTOINCREMENT," +
" name VARCHAR(50) UNIQUE NOT NULL," +
" scale_x FLOAT NOT NULL," +
" scale_y FLOAT NOT NULL," +
" trans_x FLOAT NOT NULL," +
" trans_y FLOAT NOT NULL" +
");");
cursor = db.query("tbl_images", new String[] {"name", "scale_x", "scale_y", "trans_x", "trans_y"}, "name = \"android\"", null, null, null, null);
imageView.setScaleType(ScaleType.MATRIX);
cursor.moveToNext();
if(cursor.getCount() > 0) {
Matrix matrix = new Matrix();
matrix.postScale(cursor.getFloat(1), cursor.getFloat(2));
matrix.postTranslate(cursor.getFloat(3), cursor.getFloat(4));
imageView.setImageMatrix(matrix);
}
imageView.setOnTouchListener(new Touch(imageView.getImageMatrix()));
button.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
float[] values = new float[9];
imageView.getImageMatrix().getValues(values);
ContentValues contentValues = new ContentValues();
contentValues.put("scale_x", values[Matrix.MSCALE_X]);
contentValues.put("scale_y", values[Matrix.MSCALE_Y]);
contentValues.put("trans_x", values[Matrix.MTRANS_X]);
contentValues.put("trans_y", values[Matrix.MTRANS_Y]);
if(cursor.getCount() > 0) {
long count = db.update("tbl_images", contentValues, "name = \"android\"", null);
Log.d("Update Count", "===========> " + count);
}
else {
contentValues.put("name", "android");
long count = db.insert("tbl_images", null, contentValues);
Log.d("Insert Count", "===========> " + count);
}
}
});
}
});
}
}
}

Categories

Resources