Hi I'm trying to perform Translate, Scale and Rotate on View (FrameLayout) in android.
In brief, I've a Fresco's SimpleDraweeView inside FrameLayout, as Fresco is not supporting Matrix transformations, so as an alternative I put that in FrameLayout and doing Translation, Rotation and Scaling.
I've extended FrameLayout here..
public class InteractiveFrameLayout extends FrameLayout {
private ViewTransformer mViewTransformer;
public InteractiveFrameLayout(Context context) {
super(context);
init(context);
}
public InteractiveFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public InteractiveFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Determine dimensions of 'earth' image
int baseViewWidth = (int) getResources().getDimension(R.dimen.animation_image_size);
int baseViewHeight = (int) getResources().getDimension(R.dimen.animation_image_size);
// Setup Gesture Detectors
mViewTransformer = new ViewTransformer(this, baseViewWidth, baseViewHeight);
}
public boolean onTouchEvent(MotionEvent event) {
return mViewTransformer.onTouchEvent(event) || super.onTouchEvent(event);
}
}
Below class takes view and does everything related to ViewTransformations.
public class ViewTransformer {
private View mView;
private Vector2D position;
private float scale = 1;
private float angle = 0;
private TouchManager touchManager = new TouchManager(2);
public ViewTransformer(View view, int viewWidth, int viewHeight) {
mView = view;
position = new Vector2D();
position.set(viewWidth / 2, viewHeight / 2);
}
public boolean onTouchEvent(MotionEvent event) {
try {
touchManager.update(event);
if (touchManager.getPressCount() == 1) {
position.add(touchManager.moveDelta(0));
ViewAffineOperation.moveViewTo(mView, position.getX(), position.getY());
}
else {
if (touchManager.getPressCount() == 2) {
Vector2D current = touchManager.getVector(0, 1);
Vector2D previous = touchManager.getPreviousVector(0, 1);
float currentDistance = current.getLength();
float previousDistance = previous.getLength();
if (previousDistance != 0) {
scale *= currentDistance / previousDistance;
ViewAffineOperation.scaleViewBy(mView, scale);
}
angle -= Vector2D.getSignedAngleBetween(current, previous);
ViewAffineOperation.rotateViewBy(mView, getDegreesFromRadians(angle));
}
}
mView.invalidate();
}
catch(Throwable t) {
// So lazy...
}
return true;
}
private static float getDegreesFromRadians(float angle) {
return (float)(angle * 180.0 / Math.PI);
}
public float getScale() {
return scale;
}
public float getRotationDegrees() {
return angle;
}
}
and this one
public class ViewAffineOperation {
public static void moveViewTo(View view, float focusX, float focusY) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
int midPointX = layoutParams.width >> 1;
int midPointY = layoutParams.height >> 1;
float dx = (focusX - midPointX);
float dy = (focusY - midPointY);
view.setTranslationX(view.getTranslationX() + dx);
view.setTranslationY(view.getTranslationY() + dy);
}
public static void scaleViewBy(View view, float scaleFactor) {
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
}
public static void rotateViewBy(View view, float degrees) {
view.setRotation(degrees);
}
}
The Key problem is in
view.setTranslationX(view.getTranslationX() + dx);
view.setTranslationY(view.getTranslationY() + dy);
This one is most promising thing what I've found in stack over flow.. Rotate and scale a view based on one handle in Android
Thanks a ton in Advance.
#Sasha Salauyou I'm trying to reach you to help me in finding solution.
Related
I'm in my android app I need a viewpager which slide vertically (up
down manner). For this I have made a custom viewpager & in which I'm
using the traditional viewpager & applied PageTransfirmer to make it
swipe vertically not horizontly. Everything is working fine in other
devices except One Plus 5t (andriod version 9)
My code is below:
public class VerticalViewPager extends ViewPager {
private float initialXValue;
private float initialYValue;
private float minXDifference = 200;
private float minYDifference = 100;
public static SwapListener swapListener;
public static String SwipeLeft = "left";
public static String SwipeRight = "right";
public static boolean swipeTriggered = false;
public static boolean verticalSwipeTriggered = false;
private FixedSpeedScroller mScroller = null;
private boolean enabled;
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// The majority of the magic happens here
setPageTransformer(true, new VerticalPageTransformer());
// The easiest way to get rid of the overscroll drawing that
happens on
the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
mScroller = new FixedSpeedScroller(getContext(),
new DecelerateInterpolator());
scroller.set(this, mScroller);
} catch (Exception ignored) {
}
}
/*
* Set the factor by which the duration will change
*/
public void setScrollDuration(int duration) {
mScroller.setScrollDuration(duration);
}
private class FixedSpeedScroller extends Scroller {
private int mDuration = 1000;
public FixedSpeedScroller(Context context) {super(context);}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator,
boolean flywheel) {super(context, interpolator, flywheel);}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int
duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public void setScrollDuration(int duration) {mDuration = duration;}
}
private class VerticalPageTransformer implements
ViewPager.PageTransformer {
#Override
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
}
else if (position <= 1) {
view.setAlpha(1);
// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
}
else {
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame
for any child views
//IsSwipeAllowed(ev);
return intercepted;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
IsSwipeAllowed(ev);
return super.onTouchEvent(swapXY(ev));
}
private void IsSwipeAllowed(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
initialXValue = event.getX();
initialYValue = event.getY();
}
if(event.getAction()==MotionEvent.ACTION_MOVE) {
try {
float diffX = Math.abs(event.getX() - initialXValue);
float diffy = Math.abs(event.getY() - initialYValue);
if (diffX > diffy && diffX > minXDifference) {
// swipe horizotal
if (!swipeTriggered && event.getX() > initialXValue) {
swapListener.listenSwapEvent(SwipeRight);
swipeTriggered = true;
}
else if (event.getX() < initialXValue) {
if (!HomeScreen.projectName.equals("ABMCPL") &&
CustomViewPager.IsSwipeAllowed(event) && !swipeTriggered) {
swapListener.listenSwapEvent(SwipeLeft); // to webview page
swipeTriggered = true;
}
}
}
else if (diffX < diffy && diffy > minYDifference) {
if (!verticalSwipeTriggered && event.getY() > initialYValue) {
viewPager.setCurrentItem(LandingPage.viewPager.getCurrentItem() - 1);
verticalSwipeTriggered = true;
}
else if (!verticalSwipeTriggered && event.getY() < initialYValue){
verticalSwipeTriggered = true;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
public interface SwapListener {
void listenSwapEvent (String direction);
}
}
I need to implement something like SeekBar. I created my class heir from View. In which I paint an oval on canvas. When touched, this oval goes up and down. I can not correctly calculate the position for the point (thumb).
enter image description here
here is my code, but it has already been redone many times and stopped working:
public class BalanceView extends View {
private Paint ovalPaint;
private Paint thumbPaint;
private float ovalBottom = 0f;
private Bitmap thumb;
private RectF oval1;
//params
private int maxValue = 18;
private float ovalPaintWidth = 2.0f;
private float ovalHeight = 60f;
//end params
private float touchX = 0f;
private float touchY = 0f;
private float mUnreachedRadius;
public BalanceView(Context context) {
super(context);
init(context, null);
}
public BalanceView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public BalanceView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context ctx, #Nullable AttributeSet attrs){
if (attrs != null){
}
if (ovalPaint == null){
ovalPaint = new Paint();
ovalPaint.setAntiAlias(true);
ovalPaint.setStrokeWidth(ovalPaintWidth);
ovalPaint.setStyle(Paint.Style.STROKE);
Shader shader = new SweepGradient(10, 10, new int[]{R.color.white, R.color.black, R.color.white}, null);
ovalPaint.setShader(shader);
}
if (thumbPaint == null){
thumbPaint = new Paint();
}
thumb = BitmapFactory.decodeResource(getResources(), R.drawable.move_point);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//refershPosition();
}
#Override
protected void onDraw(Canvas canvas) {
paintOval(ovalPaint, canvas);
float cos = computeCos(touchX, touchY);
float y = calcYLocationInWheel(cos);
Log.d("balance", "cos: " + cos + " y: " + y + " touchX: " + touchX + " touchY: " + touchY + "mUnreachedRadius: " + mUnreachedRadius) ;
canvas.drawBitmap(thumb, touchX, y, thumbPaint);
super.onDraw(canvas);
}
private void paintOval(Paint paint, #NonNull Canvas canvas){
if (paint != null) {
int right = getWidth() - 30;
int left = (getWidth() - right) / 2;
float top = ovalBottom - ovalHeight;
oval1 = new RectF(left, top, right + left, ovalBottom);
canvas.drawOval(oval1, paint);
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getY() >= getTop() && event.getY() <= getBottom()) {
touchY = event.getY();
}
if (event.getX() <= oval1.right - thumb.getWidth() && event.getX() >= oval1.left) {
touchX = event.getX();
}
ovalBottom = touchY;
invalidate();
return super.dispatchTouchEvent(event);
}
private float computeCos(float x, float y) {
float width = x - oval1.width() / 2;
float height = y - oval1.height() / 2;
float slope = (float) Math.sqrt(width * width + height * height);
return height / slope;
}
private float calcYLocationInWheel(double cos) {
return oval1.bottom * (float) cos;
}
}
I try so:
private float getY(float a, float b, float x){
// return (float) (Math.pow(b, 2d) * ((Math.pow(a, 2d) - Math.pow(x, 2d)) / Math.pow(a, 2d)));
return (float)
((Math.pow(b, 2d) / Math.pow(a, 2d)) * (Math.sqrt(b * b * (a * a - x * x))));
}
But the data is wrong.
I made a rotating knob ,but I want to stop the knob at specific angles for 2 seconds. I want to stop it on 260f and -20f.
Can anyone suggest how to do it ?
This is the code from a blog. I made many changes according to my requirements.
public class RotatoryKnobView extends ImageView {
private float angle = -20f;
private float theta_old=0f;
private RotaryKnobListener listener;
public interface RotaryKnobListener {
public void onKnobChanged(float arg);
}
public void setKnobListener(RotaryKnobListener l )
{
listener = l;
}
public RotatoryKnobView(Context context) {
super(context);
initialize();
}
public RotatoryKnobView(Context context, AttributeSet attrs)
{
super(context, attrs);
initialize();
}
public RotatoryKnobView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initialize();
}
private float getTheta(float x, float y)
{
float sx = x - (getWidth() / 2.0f);
float sy = y - (getHeight() / 2.0f);
float length = (float)Math.sqrt( sx*sx + sy*sy);
float nx = sx / length;
float ny = sy / length;
float theta = (float)Math.atan2( ny, nx );
final float rad2deg = (float)(180.0/Math.PI);
float thetaDeg = theta*rad2deg;
return (thetaDeg < 0) ? thetaDeg + 360.0f : thetaDeg;
}
public void initialize()
{
this.setImageResource(R.drawable.rotoron);
setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX(0);
float y = event.getY(0);
float theta = getTheta(x,y);
switch(event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_POINTER_DOWN:
theta_old = theta;
break;
case MotionEvent.ACTION_MOVE:
invalidate();
float delta_theta = theta - theta_old;
theta_old = theta;
int direction = (delta_theta > 0) ? 1 : -1;
angle += 5*direction;
notifyListener(angle+20);
break;
}
return true;
}
});
}
private void notifyListener(float arg)
{
if (null!=listener)
listener.onKnobChanged(arg);
}
protected void onDraw(Canvas c)
{if(angle==257f){
try {
synchronized (c) {
c.wait(5000);
angle=260f;
}
} catch (InterruptedException e) {
}
}
else if(angle==-16f)
{
try {
synchronized (c) {
c.wait(5000);
angle=-20f;
}
} catch (InterruptedException e) {
}
}
else
if(angle>260f)
{
angle=-20f;
}
else if(angle<-20f)
{
angle=260f;
}
else{
c.rotate(angle,getWidth()/2,getHeight()/2);
}
super.onDraw(c);
}
}
You may set a fixed angle and use postDelayed to clear it after 2 seconds.
public class RotatoryKnobView extends ImageView {
private float angle = -20f;
private float theta_old=0f;
private RotaryKnobListener listener;
private Float fixedAngle;
private float settleAngle;
private Runnable unsetFixedAngle = new Runnable() {
#Override
public void run() {
angle = settleAngle;
fixedAngle = null;
invalidate();
}
};
public interface RotaryKnobListener {
public void onKnobChanged(float arg);
}
public void setKnobListener(RotaryKnobListener l )
{
listener = l;
}
public RotatoryKnobView(Context context) {
super(context);
initialize();
}
public RotatoryKnobView(Context context, AttributeSet attrs)
{
super(context, attrs);
initialize();
}
public RotatoryKnobView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initialize();
}
private float getTheta(float x, float y)
{
float sx = x - (getWidth() / 2.0f);
float sy = y - (getHeight() / 2.0f);
float length = (float)Math.sqrt( sx*sx + sy*sy);
float nx = sx / length;
float ny = sy / length;
float theta = (float)Math.atan2( ny, nx );
final float rad2deg = (float)(180.0/Math.PI);
float thetaDeg = theta*rad2deg;
return (thetaDeg < 0) ? thetaDeg + 360.0f : thetaDeg;
}
public void initialize()
{
this.setImageResource(R.drawable.rotoron);
setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX(0);
float y = event.getY(0);
float theta = getTheta(x,y);
switch(event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_POINTER_DOWN:
theta_old = theta;
break;
case MotionEvent.ACTION_MOVE:
invalidate();
float delta_theta = theta - theta_old;
theta_old = theta;
int direction = (delta_theta > 0) ? 1 : -1;
angle += 5*direction;
notifyListener(angle+20);
break;
}
return true;
}
});
}
private void notifyListener(float arg)
{
if (null!=listener)
listener.onKnobChanged(arg);
}
void setFixedAngle(float angle, float settleAngle) {
fixedAngle = angle;
this.settleAngle = settleAngle;
postDelayed(unsetFixedAngle, 2000);
}
protected void onDraw(Canvas c)
{
if(fixedAngle==null) {
if (angle > 270) {
setFixedAngle(270, -15);
} else if (angle < -20f) {
setFixedAngle(-20, 260);
}
}
Log.d("angle", "angle: " + angle + " fixed angle: " + fixedAngle);
c.rotate(fixedAngle == null ? angle : fixedAngle,getWidth()/2,getHeight()/2);
super.onDraw(c);
}
}
`
I think the ultimate answer here is to implement your own class by extending SurfaceView and then overriding onDraw( Canvas canvas )
You can then use the Canvas routines to render your control.
There are a lot of good examples out there if you google.
To get started initialize the surface view:
// So things actually render
setDrawingCacheEnabled(true);
setWillNotDraw(false);
setZOrderOnTop(true);
// Controls the drawing thread.
getHolder().addCallback(new CallbackSurfaceView());
Override onDraw and add your rendering routines. You can layer them
as you go.
public void onDraw(Canvas canvas) {
// Always Draw
super.onDraw(canvas);
drawBackground(canvas);
drawKnobIndentWell(canvas);
drawKnob(canvas);
drawKnobLED( canvas ); //etc....
}
An example of a Callback and an update thread:
/**
* This is the drawing callback.
* It handles the creation and destruction of the drawing thread when the
* surface for drawing is created and destroyed.
*/
class CallbackSurfaceView implements SurfaceHolder.Callback {
Thread threadIndeterminant;
RunnableProgressUpdater runnableUpdater;
boolean done = false;
/**
* Kills the running thread.
*/
public void done() {
done = true;
if (null != runnableUpdater) {
runnableUpdater.done();
}
}
/**
* Causes the UI to render once.
*/
public void needRedraw() {
if (runnableUpdater != null) {
runnableUpdater.needRedraw();
}
}
/**
* When the surface is created start the drawing thread.
* #param holder
*/
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!done) {
threadIndeterminant = new Thread(runnableUpdater = new RunnableProgressUpdater());
threadIndeterminant.start();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* When the surface is destroyed stop the drawing thread.
* #param holder
*/
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (null != runnableUpdater) {
runnableUpdater.done();
threadIndeterminant = null;
runnableUpdater = null;
}
}
}
/**
* This is the runnable for the drawing operations. It is started and stopped by the callback class.
*/
class RunnableProgressUpdater implements Runnable {
boolean surfaceExists = true;
boolean needRedraw = false;
public void done() {
surfaceExists = false;
}
public void needRedraw() {
needRedraw = true;
}
#Override
public void run() {
canvasDrawAndPost();
while (surfaceExists) {
// Renders continuously during a download operation.
// Otherwise only renders when requested.
// Necessary so that progress bar and cirlce activity update.
if (syncContext.isRunning()) {
canvasDrawAndPost();
needRedraw = true;
} else if (needRedraw) {
canvasDrawAndPost();
needRedraw = false;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Don't care
}
}
// One final update
canvasDrawAndPost();
}
/**
* Routine the redraws the controls on each loop.
*/
private synchronized void canvasDrawAndPost() {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
try {
draw(canvas);
} finally {
getHolder().unlockCanvasAndPost(canvas);
}
}
}
}
If you decide to go this route you can customize your control from XML using
custom values.
<com.killerknob.graphics.MultimeterVolumeControl
android:id="#+id/volume_control"
android:layout_below="#id/divider_one"
android:background="#android:color/white"
android:layout_width="match_parent"
android:layout_height="60dp"
android:minHeight="60dp"
custom:ledShadow="#357BBB"
custom:ledColor="#357BBB"
custom:knobBackground="#color/gray_level_13"
custom:knobColor="#android:color/black"
/>
When you create a custom control you reference it by its package name.
You create custom variable in a resource file under /values and then reference
them in your class.
More details here:
http://developer.android.com/training/custom-views/create-view.html
This may be more work then you want to do, but I think you will end up with a more professional looking control and the animations will be smoother.
At any rate, looks like a fun project. Good Luck.
I have a customview , inside it , i have one view slide up and down , when slide i draw fade background like this :
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawARGB(alpha, 0, 0, 0);
super.dispatchDraw(canvas);
}
It work with my device android 4.2.2 but with android 4.4.2 or google nexus one android 2.3.7 ,it so bad.
2 images below show in my device 4.2.2(slide in and slide out):
http://imgur.com/p6i9gw8
http://imgur.com/9Sdzy7v
and 2 images below show in google nexus one android 2.3.7(slide in and slide out):
http://imgur.com/ZGKiRJi
http://imgur.com/Uf3vRdb
As you can see, in 2 image first, fade draw correct , and in other, it look bad.
Complete code for this view is :
public class SlideView extends ViewGroup {
private static final int MAXDURATIONSLIDE = 500;
protected View content;
private int childHeight;
private int childOffset;
private int childWidth;
private int alpha;
private Fillinger fillinger;
public ISlide getSlideChangeListener() {
return slideListener;
}
public void setSlideChangeListener(ISlide slideChangeListener) {
this.slideListener = slideChangeListener;
}
private ISlide slideListener;
public SlideView(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
if(attrs!=null){
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideView);
try {
int contentLayoutId = a.getResourceId(R.styleable.SlideView_slideView, 0);
DebugLog.d(contentLayoutId);
setContent(contentLayoutId);
} finally {
a.recycle();
}
}
fillinger = new Fillinger(context);
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
DebugLog.d("width "+width+" height "+height);
childOffset = height;
content.layout(0, childOffset, childWidth, childOffset + childHeight);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
measureChild(content, widthMeasureSpec, height);
childHeight = content.getMeasuredHeight();
childWidth = content.getMeasuredWidth();
DebugLog.d("childWidth "+childWidth+" childHeight "+childHeight);
setMeasuredDimension(width, height);
}
public void setContent(int resId) {
View view = LayoutInflater.from(getContext()).inflate(resId, this,false);
setContent(view);
}
public void setContent(View v) {
if (content != null)
removeView(content);
content = v;
addView(content);
}
private void moveViewByY(int diffY) {
childOffset += diffY;
alpha = (int) (Math.abs((getHeight()-childOffset)*255/(childHeight))*0.5f);
content.layout(0, childOffset, childWidth, childOffset + childHeight);
if(slideListener!=null){
slideListener.onSlide(childOffset,childHeight);
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawARGB(alpha, 0, 0, 0);
super.dispatchDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(isIn()&&touchOutSide(event)){
toogle();
return true;
}
return false;
}
private boolean touchOutSide(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
if(x<content.getLeft()||x>content.getRight()||y<content.getTop()||y>content.getBottom()){
return true;
}
return false;
}
public void hide(){
if(isIn()){
fillinger.startScroll(content.getTop(),getHeight(),childHeight,MAXDURATIONSLIDE);
}
}
public void show(){
if(!isIn()){
fillinger.startScroll(content.getTop(),getHeight()-childHeight,childHeight,MAXDURATIONSLIDE);
}
}
public void toogle(){
fillinger.cancleAnimation();
if(isIn()){
hide();
}else{
show();
}
}
public boolean isIn(){
return content.getTop()<getHeight();
}
public class Fillinger implements Runnable {
private static final String tag = "Fillinger";
private Scroller mScroller;
private int lastY;
private boolean more;
private int currentY;
private int diffY;
public Fillinger(Context context) {
mScroller = new Scroller(context);
}
public void startScroll(float startY, float endY, float maxDistance, int maxDurationForFling) {
int duration = (int) Math.min(Math.abs((endY - startY)) / maxDistance * maxDurationForFling, maxDurationForFling);
lastY = (int) startY;
if(slideListener!=null){
slideListener.onStartSlide();
}
mScroller.startScroll(0, (int) startY, 0, -(int) (endY - startY), duration);
setDrawingCacheEnabled(true);
post(this);
}
public void cancleAnimation(){
removeCallbacks(this);
}
#Override
public void run() {
more = mScroller.computeScrollOffset();
currentY = mScroller.getCurrY();
diffY = lastY - currentY;
moveViewByY(diffY);
lastY = currentY;
if (more) {
post(this);
}else{
setDrawingCacheEnabled(false);
if(slideListener!=null){
slideListener.onSlideFinish(isIn());
}
}
}
}
}
What i missing?
Sorry for my bad english.
Thanks all.
Edit: finally , i must call invalidate in moveViewByY method like this
private void moveViewByY(int diffY) {
childOffset += diffY;
alpha = (int) (Math.abs((getHeight()-childOffset)*255/(childHeight))*0.5f);
content.layout(0, childOffset, childWidth, childOffset + childHeight);
invalidate();
if(slideListener!=null){
slideListener.onSlide(childOffset,childHeight);
}
}
I dont know why , may be invalidate , view tree redraw and old canvas cleared.
Expect best solution, thanks .
I have the following situation:
ZoomAndPanLayout
|
+---> ImageView
|
+---> FrameLayout (DragLayer)
|
+---> One or more controls. A view with a circle drawn on it.
With some minor issues because I don't care for now about screen bound the ZoomAndPanLayout works. I implemented ZoomAndPan like this:
public class ZoomAndPanLayout extends FrameLayout {
//region Constants
public static final float DEFAULT_MIN_SCALE_FACTOR = 1.0f;
public static final float DEFAULT_MAX_SCALE_FACTOR = 5.0f;
// endregion Constants
// region Fields
private float translationX = 0;
private float translationY = 0;
private float pivotX = 0;
private float pivotY = 0;
private float oldX;
private float oldY;
private float scaleFactor = 1.0f;
private float minScaleFactor = ZoomAndPanLayout.DEFAULT_MIN_SCALE_FACTOR;
private float maxScaleFactor = ZoomAndPanLayout.DEFAULT_MAX_SCALE_FACTOR;
private ScaleGestureDetector scaleGestureDetector = null;
// endregion Fields
// region Constructor
public ZoomAndPanLayout(Context context) {
super(context);
this.initialize(context);
}
public ZoomAndPanLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.initialize(context);
}
public ZoomAndPanLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.initialize(context);
}
private void initialize(Context context) {
this.scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
}
// endregion Constructor
#Override
public boolean onTouchEvent(MotionEvent event) {
this.scaleGestureDetector.onTouchEvent(event);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
this.oldX = event.getX();
this.oldY = event.getY();
break;
}
case MotionEvent.ACTION_MOVE:
{
if (!this.scaleGestureDetector.isInProgress())
{
float x = event.getX();
float y = event.getY();
float deltaX = x - this.oldX;
float deltaY = y - this.oldY;
this.translationX += deltaX;
this.translationY += deltaY;
this.applyTransformations();
this.oldX = x;
this.oldY = y;
}
}
}
return true;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return this.scaleGestureDetector.onTouchEvent(event);
}
private void applyTransformations() {
final View child = this.getChildAt(0);
if (child != null)
{
child.setPivotX(this.pivotX);
child.setPivotY(this.pivotY);
child.setScaleX(this.scaleFactor);
child.setScaleY(this.scaleFactor);
// TODO: bound child to screen limits
child.setTranslationX(this.translationX);
child.setTranslationY(this.translationY);
}
}
public Rect getChildRect() {
View child = this.getChildAt(0);
if (child != null)
{
Rect outRect = new Rect();
outRect.right = (int) (child.getWidth() * child.getScaleX());
outRect.bottom = (int) (child.getHeight() * child.getScaleY());
int[] location = new int[2];
child.getLocationOnScreen(location);
outRect.offset(location[0], location[1]);
return outRect;
}
else
{
return new Rect(0, 0, 0, 0);
}
}
// region Private Inner Enums, Interfaces and Classes
private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private ZoomAndPanLayout upper = ZoomAndPanLayout.this;
#Override
public boolean onScale(ScaleGestureDetector detector) {
float newScaleFactor = detector.getScaleFactor();
float originalScaleFactor = upper.scaleFactor;
upper.scaleFactor *= newScaleFactor;
// Bound the scaleFactor to the min and max limits
if (upper.scaleFactor >= upper.maxScaleFactor)
{
upper.scaleFactor = upper.maxScaleFactor;
newScaleFactor = upper.maxScaleFactor / originalScaleFactor;
}
else if (upper.scaleFactor * newScaleFactor <= upper.minScaleFactor)
{
upper.scaleFactor = upper.minScaleFactor;
newScaleFactor = upper.minScaleFactor / originalScaleFactor;
}
// set pivot
View child = upper.getChildAt(0);
if (child != null)
{
if (newScaleFactor * child.getWidth() * upper.scaleFactor <= originalScaleFactor * child.getWidth()
|| newScaleFactor * child.getHeight() * upper.scaleFactor <= originalScaleFactor * child.getWidth())
{
upper.pivotX = newScaleFactor * child.getWidth() / 2;
upper.pivotY = newScaleFactor * child.getHeight() / 2;
}
else
{
upper.pivotX = detector.getFocusX();
upper.pivotY = detector.getFocusY();
}
}
upper.applyTransformations();
return true;
}
}
// endregion Private Inner Enums, Interfaces and Classes
}
When I create each child of DragLayer I assign to them a OnLongClickListener, but the god damn thing dose not fire. when I long click on any child of DragLayer.
Any idea how can I implement this using both my idea for ZoomAndPanLayout or any idea. If you ask yourself why I need ZoomAndPanLayout, it is because I must be able to zoom and pan any layout not just an ImageView.
Any idea?
Probably because you return true in onTouchEvent method.
When true is returned it consumed an event and children don't receive their own onTouchEvent.
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return this.scaleGestureDetector.onTouchEvent(event);
}
always return true and it also blocks children from receiving an event.