I've been working on an app with complex layouts. I recently realized I need to make parts or all of my layouts zoomable.
One of my main xml files has a linear layout with multiple layouts nested within it to positions views properly. Is there an easy way to make this linear layout and everything within in zoomable? Or would it be easier to make the entire layout file zoomable? What are my options?
First of all extend that class with that specific view
public class MyImageView extends ImageView{
Override following method .
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, midPoint.x, midPoint.y);
if(appInitialized) {
hsSide.draw(canvas);
scaleA.draw(canvas);
scaleB.draw(canvas);
}
canvas.restore();
}
Create a Gesture Detector that will detect size of zoomed object and you can limit to avoid overlaps.
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
pivotX = detector.getFocusX();
pivotY = detector.getFocusY();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.8f, Math.min(mScaleFactor, 2.0f));
invalidate();
return true;
}
}
At the end initialize the object
ScaleGestureDetector mScaleDetector;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
You might have a look at static transformations. Any ViewGroup or subclass can be customized to apply a transformation to its child views. You enable this by calling setStaticTransformationsEnabled(true), and then overriding the getChildStaticTransformation() callback (docs link) in your custom ViewGroup. You can apply any transformation you like, including a scale to create your zoom effect. This callback will be called any time the view needs to be redrawn or is invalidated.
Also, beware when using this alongside hardware acceleration. Depending on the frequency with which you need to update the transformations you may find hardware doesn't quite work to redraw as you expect. If so, you will need to enable software layers for this view hierarchy.
Related
I am trying to follow the tutorial from https://www.youtube.com/watch?v=X2ls3FTPOAc in order to zoom into my drawable which lies inside an imageview.
I see here that the scaleListener object is called since the Log outputs that it's being called. However, when I use the ImageView.ScaleType.MATRIX scale type for the image-view it doesn't seem to work. I've also added a slight modification in the onScale method. Here's my code:
view1.setScaleType(ImageView.ScaleType.MATRIX); //using CENTER works
Matrix matrix = new Matrix();
view1.setImageMatrix(matrix);
DummyDraw drawD = new DummyDraw(this);
view1.setImageDrawable(drawD);
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
a few methods down I have:
public boolean onTouchEvent(MotionEvent event){
scaleGestureDetector.onTouchEvent(event);
return true; }
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
public boolean onScale(ScaleGestureDetector detector) {
Log.d("inside1044","inside scale listener onSscale method ");
float scaleFactor = detector.getScaleFactor();
scaleFactor = Math.max(0.1f, Math.min(scaleFactor,5.0f));
scaleFactor2 = scaleFactor;//a modification I made
matrix.setScale(scaleFactor,scaleFactor);
view1.invalidateDrawable(view1.getDrawable());//this is the modification I made
return true;}
}
Now, the DummyDraw class extends Drawable which just draws a pink background and a circle in the middle of the canvas. the variable scaleFactor2 is static and defined in the MainActivity, so everytime the user touches the view the method onScale is called updating the new value for scaleFactor2. I use this scaleFactor2 value somehow to try to scale the canvas of my drawable object. Here's what the code looks like inside the drawable object, inside the onDraw():
//define some variables to get the center of the canvas
canvas.save();
canvas.scale(MainActivity.scaleFactor2,MainActivity.scaleFactor2);
//draw the circle to the screen
canvas.restore();
When I place both fingers on the screen and bring them closer together the circle jumps and moves slightly, however when I move the fingers apart nothing happens. Remember, that this works only when the ImageView.ScaleType is set to CENTER not to MATRIX.
I am trying to accomplish a smooth zoom in and out of the view.
Any ideas would be appreciated.
I have to be missing something obvious, since everyone on the planet seems to be able to get the whole pinch-zoom thing to work. The main difference is most folks seem to be zooming on images, whereas I've a GridLayout (android-support-v7-gridlayout) with a bunch of text views. The goal seems simple enough: Allow the user to zoom in so that they can view a portion of the full grid at a larger size.
The grid draws fine, and horizontal & vertical scrolling work fine. And then, when I do a two-finger gesture with fingers moving apart (zoom), nothing happens, despite getting the log message that onScale has been called. When I move the two fingers in (pinch), all the views disappear (though my scroll bars are left).
I've looked at Matrix transformations but haven't seen that those work on anything but an image view.
The basic layout xml:
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<HorizontalScrollView
android:id="#+id/chooser_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</HorizontalScrollView>
</ScrollView>
Then in the activity
private ZoomGridLayout playerGrid;
and in onCreate:
ViewGroup gridParent = (ViewGroup) findViewById(R.id.chooser_layout);
playerGrid = new ZoomGridLayout(getBaseContext());
playerGrid.setRowCount(8);
playerGrid.setColumnCount(8);
LayoutParams layout =
new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
playerGrid.setDetector();
gridParent.addView(playerGrid, layout);
I then proceed to add ~150 small text views to the grid.
Here's the first take at ZoomGridLayout:
public class ZoomGridLayout extends GridLayout {
private final static String TAG = "ZGL";
private ScaleGestureDetector scaleDetector;
private Context context;
public void setDetector() {
scaleDetector = new ScaleGestureDetector(context, new ScaleGesture());
}
public ZoomGridLayout(Context c) {
super(c);
context = c;
}
#Override
public boolean onTouchEvent (MotionEvent event) {
scaleDetector.onTouchEvent(event);
return true;
}
private class ScaleGesture
extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
#Override
public boolean onScale(ScaleGestureDetector detector) {
Log.v(TAG, detector.toString());
ScaleAnimation scale =
new ScaleAnimation(detector.getPreviousSpan(),
detector.getCurrentSpan(),
detector.getPreviousSpan(),
detector.getCurrentSpan());
scale.setFillAfter(true);
startAnimation(scale);
return true;
}
}
}
I've been beating my head against this for a couple days now. I'm new to Android programming, so not very familiar with the libraries, but I'm an experienced developer. Any pointers would be wonderful. TIA.
I gave up on animations (though eventually I'd like to try to make that work). Here's the new onScale method:
#Override
public boolean onScale(ScaleGestureDetector detector) {
Log.v(TAG, detector.toString());
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(MIN_SCALE, Math.min(mScaleFactor, MAX_SCALE));
invalidate();
return true;
}
this is then complemented by overriding the draw method:
#Override
public void dispatchDraw(Canvas canvas) {
String disp = String.valueOf(mScaleFactor);
Log.v(TAG, "dispatch draw " + disp);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor);
super.dispatchDraw(canvas);
canvas.restore();
}
i also learned that since the parent is a ViewGroup instead of a View, that one needs to override the dispatch… methods instead of the on… methods.
there are so many different variants of this pattern out there. i hope this particular wrinkle helps someone else.
I have drawn a bitmap image over a canvas.
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.sq);
canvas.drawColor(color.black);
Rect dstRectForRender = new Rect(0,0,320,450);
canvas.drawBitmap(image, null,dstRectForRender,null);
The image gets displayed based on my screnn on a cnavs.
On my touch input, I need to pass the x and y co-ordinate position of the image and fill that pixel with a color to show that the image is painted on a drag event.
How can I pass the x and y coo-ordinate parameters? Which functions should I use to plot the pixels on the image?
I appreciate your help and sweet time.
I'm not sure if this is the best way to do this, but it would be much easier to do this if you defined your own subclass of ImageView and name it something like DrawableImageView. You'd have to make sure you implement all the basic constructors from ImageView, then override the onTouchEvent method. From that event you can get the touch coordinates and store them in an ArrayList<Point> and use that ArrayList by overriding the onDraw method and "painting" the image.
public class DrawableImageView extends ImageView {
ArrayList<Point> list = new ArrayList<Point>();
//constructors..
#Override
public boolean onTouchEvent (MotionEvent event) {
float x = event.getX();
float y = event.getY();
list.add(new Point(x,y));
invalidate();
}
This is just a very brief overview of how to start your class, and may not be the most accurate way of doing things (depending on your specific code). Now, instead of using <ImageView> tags in your xml (or, loading an ImageView programatically), you refer to your subclass like so:
<your.package.name.DrawableImageView
/>
Edit
In response to your comment, there is no predetermined way to draw over an image. You must implement this yourself, which is why I recommended storing Points in an ArrayList. I'm not really sure what you're trying to achieve here, but to draw (for example) black dots over an image you have to override onDraw:
public void onDraw(Canvas c) {
super.onDraw(c);
for(Point p : list) {
//Draw black point at x and y.. I'm posting from my cell so I can't go into much detail
}
}
Also, to force a View to redraw itself you need to use the invalidate() method in your onTouchEvent() (which I've added above).
I have a FrameLayout that contains several ImageView. On the main activity, I record the touch events in order to move my FrameLayout and the images inside with the finger (drag).
For doing so, I am calling canvas.translate(x,y) inside the onDraw of the framelayout which is called by a invalidate() in the activity touch event handler.
Everything works like a charm except that after the translate, I am not able to click on my ImageView. In fact, the click listener of each image is still at the original place before the translate.
I have read that I should manually update the layout of each image after the translate but how to do that ? If I change the margin with the translate value, the images are going two times further ...
I would really appreciate any help on that one.
Cheers.
Here is the frameLayout where I translate the canvas in the onDraw() method (the ImageView are added to that FrameLayout in my main Activity).
public class TopView extends FrameLayout {
public float mPosX = 0;
public float mPosY = 0;
public TopView(Context context)
{
super(context);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(1920, 3200, Gravity.CENTER);
this.setLayoutParams(lp);
setWillNotDraw(false);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(this.mPosX, this.mPosY);
}
}
You can use setPadding(this.mPosX,this.mPosY,0,0) in the the constructor. It should work.
I need to scale View by setting the scale factor in percents. I need to set exactly the same param as ScaleAnimation use, because I'm going to scale it using ScaleAnimation in the future. How do I do it?
Thanks
I've solved the problem by creating animation xml file with duration=0, which sets desired parameters. It is also possible to create such animation via code. Then to apply it, just use:
Animation animationInit = AnimationUtils.loadAnimation(context, R.anim.gallery_init);
v.startAnimation(animationInit);
This will allow you to use straight up float values (percent) when handeling drawing and placements. Refer to: this lovely site which really helps with views.
public class MyView extends View {
...
#Override public void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale((getWidth() > getHeight()) ? getHeight() : getWidth());
// Your other code
canvas.restore();
}
...
}