I have multiple buttons in my LinearLayout on my activity_main, and want to detect which Button is closest to the users finger. When the users moves his finger in a single gesture the closest button should highlight and when he removes his finger the closest button should do its onClick function.
It should act like the default keyboard on android or the iphone calculator. It selects the button closest to the finger. When you drag your finger across it will change the selection to the closest key and only when you release your finger does it do the onClick function.
Referencing Get button coordinates and detect if finger is over them - Android
I got to the point where selection works, but only if I tap anywhere that isn't a button and it doesn't work to select closest button when not over a button.
(Programming for API 21 in case thats important)
activity_main.xml
<TextView/>
<ButtonLayout>
<LinearLayout1>
<Button1/>
<Space/>
<Button2/>
<Space/>
<Button3/>
<Space/>
<Button4/>
</LinearLayout1>
<LinearLayout2>
<Button5/>
<Space/>
<Button6/>
<Space/>
<Button7/>
<Space/>
<Button8/>
</LinearLayout2>
</OuterLinearLayout>
Java
private View.OnTouchListener buttonLayoutTouchListener= new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
//OuterLayout
for (int i = 0; i < buttonLayout.getChildCount(); i++) {
View inner = buttonLayout.getChildAt(i);
//Inner
if(inner instanceof LinearLayout) {
for (int j = 0;j<((LinearLayout) inner).getChildCount();j++) {
View current = ((LinearLayout) inner).getChildAt(j);
if (current instanceof Button) {
Button b = (Button) current;
Rect rect = new Rect();
b.getGlobalVisibleRect(rect);
//factors for textview
rect.top-=300;
rect.bottom-=300;
if (!isPointWithin(x, y, rect.left, rect.right, rect.top,
rect.bottom)) {
b.getBackground().setState(defaultStates);
}
if (isPointWithin(x, y, rect.left, rect.right, rect.top,
rect.bottom)) {
if (b != mLastButton) {
mLastButton = b;
b.getBackground().setState(STATE_PRESSED);
//highlight button finger currently over
Log.d("button",mLastButton.getText().toString());
}
}
}
}
}
}
return true;
}
};
static boolean isPointWithin(int x, int y, int x1, int x2, int y1, int y2) {
return (x <= x2 && x >= x1 && y <= y2 && y >= y1);
}
Make a new class to extend LinearLayout and override onInterceptTouchEvent. Set your outer LinearLayout to the new class.
Warning: This has a side effect of calling onClick twice if you just tap a button (Once on parent, once on child). Here is my super dirty workaround. Please consider finding a real workaround and posting a reply.
Double Click workaround
//call if(doubleClick()) in buttons' onClicklistener
public mLastClickTime=0;
public boolean doubleClick() {
//29 is arbitrary
if (SystemClock.elapsedRealtime() - mLastClickTime < 29) {
return true;
}
mLastClickTime = SystemClock.elapsedRealtime();
return false;
}
ExtendedLinearLayout
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class ButtonLayout extends LinearLayout {
public int layoutTop;
public ButtonLayout(Context context) {
super(context);
}
public ButtonLayout(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public ButtonLayout(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ButtonLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Rect rect = new Rect();
this.getGlobalVisibleRect(rect);
layoutTop =rect.top;
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Button keep=null;
int x = (int) ev.getX();
int y = (int) ev.getY();
int count = 0;
//number of buttons
int buttonMax =0;
int distance = Integer.MAX_VALUE;
//Outer LinearLayout
outerLoop:
for (int i = 0; i < this.getChildCount(); i++) {
View inner = this.getChildAt(i);
if (inner instanceof LinearLayout) {
//Inner LinearLayout
for (int j = 0; j < ((LinearLayout) inner).getChildCount(); j++) {
View current = ((LinearLayout) inner).getChildAt(j);
if (current instanceof Button) {
buttonMax++;
Button b = (Button) current;
Rect rect = new Rect();
b.getGlobalVisibleRect(rect);
rect.top -= layoutTop;
rect.bottom -= layoutTop;
//finger in button
if (isPointWithin(x, y, rect.left, rect.right, rect.top,
rect.bottom)) {
b.setPressed(true);
keep=b;
break outerLoop;
}else{
b.setPressed(false);
count++;
int buttonDistance = distance(x, y, rect.left, rect.right, rect.top, rect.bottom);
if(buttonDistance<distance){
keep=b;
distance=buttonDistance;
}
}
}
}
}
}
//if non are selected let button be selected
if(count==buttonMax){
keep.setPressed(true);
}
//on release
if(ev.getAction()==MotionEvent.ACTION_UP){
keep.callOnClick();
return false;
}
return super.onInterceptTouchEvent(ev);
}
static boolean isPointWithin(int x, int y, int x1, int x2, int y1, int y2) {
return (x <= x2 && x >= x1 && y <= y2 && y >= y1);
}
static int distance(int x, int y,int x1, int x2, int y1, int y2){
x1 = Math.abs(x1-x);
x2 = Math.abs(x2-x);
y1 = Math.abs(y1-y);
y2 = Math.abs(y2-y);
x = (x1>x2?x2:x1);
y = (y1>y2?y2:y1);
return x+y;
}
}
Related
I have tried to implement the youtube like draggable panel using the Customlayout which extends ViewGroup. I have made use of the onTouchEvents to create and scaling on dragging effect.
Now, the problem is that the when dragged down, the selected item resides near the bottom of the screen as it happens in YouTube. When clicked on that item in that position, it should get back to the full screen mode. But to do so, I implemented the onClicklistener for the view but it simply doesn't respond.
Also, when at the bottom, how do I restrict it's motion in the vertical direction and allow it to move in the horizontal direction as it happens in YouTube.
Here is the CustomLayout class called DraggablePanel.
package visio.com.eventpage;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.Toast;
import static android.view.MotionEvent.INVALID_POINTER_ID;
/**
* Created by Rambo on 1/9/2018.
*/
public class DraggablePanel extends ViewGroup {
public final ViewDragHelper mDragHelper;
public View mHeaderView;
private View mDescView;
private float mInitialMotionX;
private float mInitialMotionY;
private int mDragRange;
private int mTop;
private float mDragOffset;
private Context context;
public int toponViewReleased;
public int screenWidth, screenHeight;
private String TAG = "DraggablePanel";
public void manualInvalidate(){
invalidate();
}
public DraggablePanel(Context context) {
this(context, null);
}
public DraggablePanel(Context context, AttributeSet attrs) {
this(context, attrs, 0);
this.context = context;
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mHeaderView = findViewById(R.id.viewHeader);
mDescView = findViewById(R.id.viewDesc);
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
screenHeight = displayMetrics.heightPixels;
screenWidth = displayMetrics.widthPixels;
}
public DraggablePanel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
}
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;
float paddingOffset = (float)top/10;
mDragOffset = (float) top / mDragRange;
mHeaderView.setPivotX(mHeaderView.getWidth());
mHeaderView.setPivotY(mHeaderView.getHeight());
mHeaderView.setScaleX(1 - mDragOffset / 2);
mHeaderView.setScaleY(1 - mDragOffset / 2);
mHeaderView.setPadding(0,0,(int) paddingOffset, (int) paddingOffset);
mDescView.setAlpha(1 - mDragOffset);
if( top == 0){
}
requestLayout();
Log.d("onViewPositionChanged","Called");
}
#Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int top = releasedChild.getTop()>screenHeight/3?screenHeight-150 - mHeaderView.getHeight():0;
Log.d("Heights","H1"+getHeight() + "child height" + releasedChild.getMeasuredHeight()+"top" +top);
mDragHelper.smoothSlideViewTo(releasedChild, releasedChild.getLeft(),top);
if( top!=0 ){
mDescView.setAlpha(0);
}
ViewCompat.postInvalidateOnAnimation(releasedChild);
toponViewReleased = top;
invalidate();
}
#Override
public int getViewVerticalDragRange(View child) {
return mDragRange;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
float descViewAlpha = mDescView.getAlpha();
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - mHeaderView.getHeight() - 20;
int newTop = Math.min(Math.max(top, topBound), bottomBound);
if( descViewAlpha == 0 ){
newTop = 0;
}
Log.d(TAG,"clampVertical" + newTop);
return newTop;
}
#Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int rightBound = mHeaderView.getPaddingRight();
final int leftBound = screenWidth - mHeaderView.getWidth() - 20;
int newTop = Math.min(Math.max(left, rightBound), leftBound);
if(mDescView.getAlpha() != 0){
newTop = 0;
}
Log.d(TAG,"clampHorizontal" + newTop);
return newTop;
}
}
#Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
if (( action != MotionEvent.ACTION_DOWN)) {
mDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
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 & ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
break;
}
}
return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);
}
#Override
public boolean performClick() {
super.performClick();
if(toponViewReleased !=0){
mDragHelper.smoothSlideViewTo(mHeaderView, 0,0);
invalidate();
Log.d("DraggablePanel","Clicked top != 0");
}else{
Log.d("DraggablePanel","Clicked top == 0");
}
return true;
}
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) {
Log.d(TAG,"onLayout Called");
mDragRange = getHeight() - mHeaderView.getHeight();
mHeaderView.layout(
0,
mTop,
r,
mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(
0,
mTop + mHeaderView.getMeasuredHeight(),
r,
mTop + b);
}
}
Here is the Layout where the above class has been used.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerViewEvents"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<visio.com.eventpage.DraggablePanel
android:id="#+id/draggablePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="#+id/viewHeader"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginEnd="20dp"
android:gravity="center"
android:scaleType="fitXY"
android:src="#drawable/ic_event" />
<TextView
android:id="#+id/viewDesc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF00FF"
android:gravity="center"
android:tag="desc"
android:text="Loreum Loreum"
android:textColor="#android:color/white"
android:textSize="35sp" />
</visio.com.eventpage.DraggablePanel>
</FrameLayout>
I am working on an application similar to Amaziograph of iPhone also known as kaleidoscope or mandala.
Till now I have tried and have made one particular layout of that app
I have extended the canvas and have made a custom canvas, where I have divided canvas in 9 parts similar to image and in draw method I have rotated the canvas and copied its content in for loop. Here is my canvas class code for above circular division shape
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.madala.mandaladrawing.R;
import com.madala.mandaladrawing.model.DrawingEvent;
import com.madala.mandaladrawing.utils.Common;
public class CanvasView extends View {
private final Context context;
private Bitmap bitmap;
private Canvas bitmapCanvas;
private Paint bitmapPaint;
private Path path = new Path();
private Paint brushPaint;
private int numberOfMirror = 5;
private int cx, cy;
public CanvasView(Context context) {
super(context);
this.context = context;
init();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public CanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
brushPaint = createPaint();
brushPaint.setColor(0xffffffff);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
canvas.drawPath(path, brushPaint);
for (int i = 1; i < numberOfMirror; i++) {
canvas.rotate(360f / numberOfMirror, cx, cy);
canvas.drawPath(path, brushPaint);
}
}
public void clearCanvas(){
bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
bitmapPaint = new Paint(Paint.DITHER_FLAG);
cx = w / 2;
cy = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
path.lineTo(x, y);
drawToCanvas(path, brushPaint);
path.reset();
break;
default:
return false;
}
invalidate();
return true;
}
private void drawToCanvas(Path path, Paint brushPaint) {
bitmapCanvas.drawPath(path, brushPaint);
for (int i = 1; i < numberOfMirror; i++) {
bitmapCanvas.rotate(360f / numberOfMirror, cx, cy);
bitmapCanvas.drawPath(path, brushPaint);
}
}
public int getCurrentBrushColor() {
return brushPaint.getColor();
}
public void setCurrentBrushColor(int color) {
brushPaint.setColor(color);
}
private Paint createPaint() {
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setStrokeWidth(8f);
p.setStyle(Paint.Style.STROKE);
p.setStrokeJoin(Paint.Join.ROUND);
p.setStrokeCap(Paint.Cap.ROUND);
return p;
}
}
I am not able to achieve the functionality for below figure
I want to draw in one box and it should be copied in the other boxes. How could this be achieved a small guide will also be helpful?
Maybe you can save traces into array, and paste it to other drawing views
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//save initial x,y into array or send it to other canvas
/*public general variable*/
String Drawing +="[[x,y]";
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
//save middle x,y into array or send it to other canvas
String Drawing +=",[x,y]";
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
//save last point into array or send it to other canvas
String Drawing +=",[x,y]]";
path.lineTo(x, y);
drawToCanvas(path, brushPaint);
path.reset();
break;
default:
return false;
}
invalidate();
return true;
}
the string resultant should be all user traces
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 1
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 2
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 3
etc..
trace1 + trace2 +trace 3 = thing drawed by user.
when you want or maybe in real time, you can send the points drawed on this view to other view... or when users end writing send the string and extract the points...
Well it's just an idea, Hope i helped ;)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- this has changed from first answer -->
<dimen name="translation_size">30dp</dimen>
</resources>
...
int size = getResources().getDimensionPixelSize(R.dimen.translation_size);
...
private void drawToCanvas(Path path, Paint brushPaint) {
bitmapCanvas.drawPath(path, brushPaint); // just render normally
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, brushPaint);
int xMax = getWidth() / size;
int yMax = getHeight() / size;
int xMin = -xMax;
int yMin = -yMax;
for (int x = xMin; x <= xMax; x++) {
for (int y = yMin; y <= yMax; y++) {
if ((Math.abs(x % 6) == 0 && Math.abs(y % 4) == 0) ||
(Math.abs(x % 6) == 3 && Math.abs(y % 4) == 2)) {
int xs = x * size;
int ys = y * size;
canvas.translate(xs, ys);
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
canvas.translate(-xs, -ys);
}
}
}
}
You don't have to use resources like I did, but if you hard-code a size into your app, make sure you multiply by the screen density to get the correct pixel offsets.
I am using a custom layout to display a varied number of buttons and intercept their clicks. Here is the source code for the custom layout:
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
public class FlowLayout extends AdapterView<Adapter> {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private static final int INVALID_INDEX = -1;
private static final int TOUCH_STATE_RESTING = 0;
private static final int TOUCH_STATE_CLICK = 1;
private int mTouchState = TOUCH_STATE_RESTING;
private Rect mRect;
private Runnable mLongPressRunnable;
private int mTouchStartX;
private int mTouchStartY;
private int horizontalSpacing = 0;
private int verticalSpacing = 0;
private int orientation = 0;
private boolean debugDraw = false;
private Adapter mAdapter;
private final AdapterObserver mObserver = new AdapterObserver();
public FlowLayout(Context context) {
super(context);
this.readStyleParameters(context, null);
}
public FlowLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
this.readStyleParameters(context, attributeSet);
}
public FlowLayout(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
this.readStyleParameters(context, attributeSet);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft();
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft();
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int size;
int mode;
if (orientation == HORIZONTAL) {
size = sizeWidth;
mode = modeWidth;
} else {
size = sizeHeight;
mode = modeHeight;
}
int lineThicknessWithSpacing = 0;
int lineThickness = 0;
int lineLengthWithSpacing = 0;
int lineLength;
int prevLinePosition = 0;
int controlMaxLength = 0;
int controlMaxThickness = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(
MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth),
MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight)
);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int hSpacing = this.getHorizontalSpacing(lp);
int vSpacing = this.getVerticalSpacing(lp);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childLength;
int childThickness;
int spacingLength;
int spacingThickness;
if (orientation == HORIZONTAL) {
childLength = childWidth;
childThickness = childHeight;
spacingLength = hSpacing;
spacingThickness = vSpacing;
} else {
childLength = childHeight;
childThickness = childWidth;
spacingLength = vSpacing;
spacingThickness = hSpacing;
}
lineLength = lineLengthWithSpacing + childLength;
lineLengthWithSpacing = lineLength + spacingLength;
boolean newLine = lp.newLine || (mode != MeasureSpec.UNSPECIFIED && lineLength > size);
if (newLine) {
prevLinePosition = prevLinePosition + lineThicknessWithSpacing;
lineThickness = childThickness;
lineLength = childLength;
lineThicknessWithSpacing = childThickness + spacingThickness;
lineLengthWithSpacing = lineLength + spacingLength;
}
lineThicknessWithSpacing = Math.max(lineThicknessWithSpacing, childThickness + spacingThickness);
lineThickness = Math.max(lineThickness, childThickness);
int posX;
int posY;
if (orientation == HORIZONTAL) {
posX = getPaddingLeft() + lineLength - childLength;
posY = getPaddingTop() + prevLinePosition;
} else {
posX = getPaddingLeft() + prevLinePosition;
posY = getPaddingTop() + lineLength - childHeight;
}
lp.setPosition(posX, posY);
controlMaxLength = Math.max(controlMaxLength, lineLength);
controlMaxThickness = prevLinePosition + lineThickness;
}
if (orientation == HORIZONTAL) {
this.setMeasuredDimension(resolveSize(controlMaxLength, widthMeasureSpec), resolveSize(controlMaxThickness, heightMeasureSpec));
} else {
this.setMeasuredDimension(resolveSize(controlMaxThickness, widthMeasureSpec), resolveSize(controlMaxLength, heightMeasureSpec));
}
}
private int getVerticalSpacing(LayoutParams lp) {
int vSpacing;
if (lp.verticalSpacingSpecified()) {
vSpacing = lp.verticalSpacing;
} else {
vSpacing = this.verticalSpacing;
}
return vSpacing;
}
private int getHorizontalSpacing(LayoutParams lp) {
int hSpacing;
if (lp.horizontalSpacingSpecified()) {
hSpacing = lp.horizontalSpacing;
} else {
hSpacing = this.horizontalSpacing;
}
return hSpacing;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
}
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean more = super.drawChild(canvas, child, drawingTime);
this.drawDebugInfo(canvas, child);
return more;
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attributeSet) {
return new LayoutParams(getContext(), attributeSet);
}
#Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
private void readStyleParameters(Context context, AttributeSet attributeSet) {
TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout);
try {
horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 0);
verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 0);
orientation = a.getInteger(R.styleable.FlowLayout_orientation, HORIZONTAL);
debugDraw = a.getBoolean(R.styleable.FlowLayout_debugDraw, false);
} finally {
a.recycle();
}
}
private void drawDebugInfo(Canvas canvas, View child) {
if (!debugDraw) {
return;
}
Paint childPaint = this.createPaint(0xffffff00);
Paint layoutPaint = this.createPaint(0xff00ff00);
Paint newLinePaint = this.createPaint(0xffff0000);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.horizontalSpacing > 0) {
float x = child.getRight();
float y = child.getTop() + child.getHeight() / 2.0f;
canvas.drawLine(x, y, x + lp.horizontalSpacing, y, childPaint);
canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y - 4.0f, x + lp.horizontalSpacing, y, childPaint);
canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y + 4.0f, x + lp.horizontalSpacing, y, childPaint);
} else if (this.horizontalSpacing > 0) {
float x = child.getRight();
float y = child.getTop() + child.getHeight() / 2.0f;
canvas.drawLine(x, y, x + this.horizontalSpacing, y, layoutPaint);
canvas.drawLine(x + this.horizontalSpacing - 4.0f, y - 4.0f, x + this.horizontalSpacing, y, layoutPaint);
canvas.drawLine(x + this.horizontalSpacing - 4.0f, y + 4.0f, x + this.horizontalSpacing, y, layoutPaint);
}
if (lp.verticalSpacing > 0) {
float x = child.getLeft() + child.getWidth() / 2.0f;
float y = child.getBottom();
canvas.drawLine(x, y, x, y + lp.verticalSpacing, childPaint);
canvas.drawLine(x - 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint);
canvas.drawLine(x + 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint);
} else if (this.verticalSpacing > 0) {
float x = child.getLeft() + child.getWidth() / 2.0f;
float y = child.getBottom();
canvas.drawLine(x, y, x, y + this.verticalSpacing, layoutPaint);
canvas.drawLine(x - 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint);
canvas.drawLine(x + 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint);
}
if (lp.newLine) {
if (orientation == HORIZONTAL) {
float x = child.getLeft();
float y = child.getTop() + child.getHeight() / 2.0f;
canvas.drawLine(x, y - 6.0f, x, y + 6.0f, newLinePaint);
} else {
float x = child.getLeft() + child.getWidth() / 2.0f;
float y = child.getTop();
canvas.drawLine(x - 6.0f, y, x + 6.0f, y, newLinePaint);
}
}
}
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(2.0f);
return paint;
}
public static class LayoutParams extends ViewGroup.LayoutParams {
private static int NO_SPACING = -1;
private int x;
private int y;
private int horizontalSpacing = NO_SPACING;
private int verticalSpacing = NO_SPACING;
private boolean newLine = false;
public LayoutParams(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
this.readStyleParameters(context, attributeSet);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams layoutParams) {
super(layoutParams);
}
public boolean horizontalSpacingSpecified() {
return horizontalSpacing != NO_SPACING;
}
public boolean verticalSpacingSpecified() {
return verticalSpacing != NO_SPACING;
}
public void setHorizontalSpacing(int hs) {
horizontalSpacing = hs;
}
public void setVerticalSpacing(int vs) {
verticalSpacing = vs;
}
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
private void readStyleParameters(Context context, AttributeSet attributeSet) {
TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout_LayoutParams);
try {
horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_horizontalSpacing, NO_SPACING);
verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_verticalSpacing, NO_SPACING);
newLine = a.getBoolean(R.styleable.FlowLayout_LayoutParams_layout_newLine, false);
} finally {
a.recycle();
}
}
}
#Override
public Adapter getAdapter() {
return mAdapter;
}
#Override
public View getSelectedView() {
throw new UnsupportedOperationException("Not supported");
}
#Override
public void setAdapter(Adapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mObserver);
refresh();
}
public void refresh() {
removeAllViewsInLayout();
for (int i = 0; i < mAdapter.getCount(); i++) {
final View view = mAdapter.getView(i, null, this);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
addViewInLayout(view, i, params, true);
}
postInvalidate();
requestLayout();
}
public class AdapterObserver extends DataSetObserver {
#Override
public void onChanged() {
refresh();
}
#Override
public void onInvalidated() {
}
}
#Override
public void setSelection(int position) {
throw new UnsupportedOperationException("Not supported");
}
// Touch detection
#Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_CLICK) {
clickChildAt((int)event.getX(), (int)event.getY());
}
endTouch();
break;
default:
endTouch();
break;
}
return true;
}
/**
* Sets and initializes all things that need to when we start a touch
* gesture.
*
* #param event The down event
*/
private void startTouch(final MotionEvent event) {
mTouchStartX = (int)event.getX();
mTouchStartY = (int)event.getY();
startLongPressCheck();
mTouchState = TOUCH_STATE_CLICK;
}
private void startLongPressCheck() {
if (mLongPressRunnable == null) {
mLongPressRunnable = new Runnable() {
public void run() {
if (mTouchState == TOUCH_STATE_CLICK) {
final int index = getContainingChildIndex(mTouchStartX, mTouchStartY);
if (index != INVALID_INDEX) { longClickChild(index); mTouchState = TOUCH_STATE_RESTING; }
}
}
};
}
postDelayed(mLongPressRunnable, 300); //ViewConfiguration.getLongPressTimeout()
}
private void longClickChild(final int index) {
final View itemView = getChildAt(index);
final int position = index;
final long id = mAdapter.getItemId(position);
final OnItemLongClickListener listener = getOnItemLongClickListener();
if (listener != null) { listener.onItemLongClick(this, itemView, position, id); }
}
private int getContainingChildIndex(final int x, final int y) {
if (mRect == null) { mRect = new Rect(); }
for (int index = 0; index < getChildCount(); index++) {
getChildAt(index).getHitRect(mRect);
if (mRect.contains(x, y)) { return index; }
}
return INVALID_INDEX;
}
private void endTouch() {
removeCallbacks(mLongPressRunnable);
mTouchState = TOUCH_STATE_RESTING;
}
private void clickChildAt(final int x, final int y) {
final int index = getContainingChildIndex(x, y);
if (index != INVALID_INDEX) {
final View itemView = getChildAt(index);
final int position = index;
final long id = mAdapter.getItemId(position);
performItemClick(itemView, position, id);
}
}
}
This code works on my test device which is a Google Nexus S with Android 4.1.2, meaning that the buttons are clickable. Yet I got reports that the buttons are unresponsive on other devices such as Android Casio C771 with Android version 2.3.3 and Verizon LG VS840 with Android 4.0.4.
Can you please tell me what could cause this discrepancy and how can I fix it?
Thank you
Things to look out for:
It is essential that the Views created for the adapter are enabled for click and touch action. After they are created:
View.setEnabled(true);
When you create the AdapterView and the BaseApapter, remember to both setOnItemClickListener and setOnItemLongClickListener if you want to handle long clicks, which it seems you do.
In the endTouch routine, you need to set mLongPressRunnable to null, so it will be created for the next touch (or lose the null check in startLongPressCheck and handle concurency issues another way).
The runnable created in startLongPressCheck needs to call endTouch when complete to reset everything to the right, nothing pending, state after the long press time period.
In the onTouchEvent switch statement, it is important to not call your endTouch routine, because this means that movement events may stop the click.
There is a suggestion (see Android onClick method doesn't work on a custom view) that if you don't call super.onTouchEvent(event) in your onTouchEvent it can cause problems. This would look something like:
#Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return super.onTouchEvent(event);
}
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
handled = true;
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_CLICK) {
clickChildAt((int)event.getX(), (int)event.getY());
handled = true;
}
endTouch();
break;
default:
// Do not do anything dramatic here because the system
// may send different (eg. motion) information here which
// may lead to you losing valid clicks if you simply cancel
// the click in process.
break;
}
if (handled == false) handled = super.onTouchEvent(event);
return handled;
}
I have one activity which is having linear layout and inside this layout there are several linear layouts and each linear layout is having set if buttons and text views. I want to achieve multi touch feature for whole screen means if user perform zoom in-zoom out using his finger then it should zoom in and zoom out whole screen(increase and decrease all buttons,text views size accordingly once).
How to achieve it using android 2.1?
regards,
Piks
This might give you an idea:
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
//import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
public class MultitouchView extends View {
private static final int STROKE_WIDTH = 1;
private static final int CIRCLE_RADIUS = 20;
private ArrayList<PointF> touchPoints = null;
private Paint drawingPaint = null;
private boolean isMultiTouch = false;
private int pathEffectPhase = 0;
public MultitouchView(Context context) {
super(context);
initialize(context);
}
public MultitouchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context);
}
public MultitouchView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(touchPoints.size() > 0)
{
DashPathEffect effect = new DashPathEffect(new float[] {7,7}, pathEffectPhase);
PointF midpt = null;
drawingPaint.setPathEffect(effect);
for(int index=1; index<touchPoints.size(); ++index)
{
midpt = getMidPoint(
touchPoints.get(index - 1).x,touchPoints.get(index - 1).y,
touchPoints.get(index).x,touchPoints.get(index).y);
canvas.drawCircle(
touchPoints.get(index - 1).x,touchPoints.get(index - 1).y,
1, drawingPaint);
canvas.drawCircle(
touchPoints.get(index - 1).x,touchPoints.get(index - 1).y,
CIRCLE_RADIUS, drawingPaint);
canvas.drawCircle(touchPoints.get(index).x,touchPoints.get(index).y,
1, drawingPaint);
canvas.drawCircle(touchPoints.get(index).x,touchPoints.get(index).y,
CIRCLE_RADIUS, drawingPaint);
canvas.drawLine(
touchPoints.get(index - 1).x,touchPoints.get(index - 1).y,
touchPoints.get(index).x,touchPoints.get(index).y,
drawingPaint);
canvas.drawCircle(midpt.x,midpt.y, 10, drawingPaint);
}
++pathEffectPhase;
invalidate();
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int action = event.getAction() & MotionEvent.ACTION_MASK;
switch(action)
{
case MotionEvent.ACTION_DOWN:
{
invalidate();
break;
}
case MotionEvent.ACTION_POINTER_DOWN:
{
isMultiTouch = true;
setPoints(event);
invalidate();
break;
}
case MotionEvent.ACTION_POINTER_UP:
{
isMultiTouch = false;
break;
}
case MotionEvent.ACTION_MOVE:
{
if(isMultiTouch)
{
setPoints(event);
invalidate();
}
break;
}
}
return true;
}
private void initialize(Context context){
drawingPaint = new Paint();
drawingPaint.setColor(Color.RED);
drawingPaint.setStrokeWidth(STROKE_WIDTH);
drawingPaint.setStyle(Paint.Style.STROKE);
drawingPaint.setAntiAlias(true);
touchPoints = new ArrayList<PointF>();
}
public void setPoints(MotionEvent event){
touchPoints.clear();
int pointerIndex = 0;
for(int index=0; index<event.getPointerCount(); ++index)
{
pointerIndex = event.getPointerId(index);
touchPoints.add(new PointF(event.getX(pointerIndex),event.getY(pointerIndex)));
}
}
private PointF getMidPoint(float x1,float y1, float x2, float y2) {
PointF point = new PointF();
float x = x1 + x2;
float y = y1 + y2;
point.set(x / 2, y / 2);
return point;
}
}
Here is the custom class of GradientTextView provided by #koush (source is there on (github)
Now I got the class but how to call this class anyone can help since I am new bee on android
As far as I understood it can use custom attribute to but nyways how to call this class from MainActivity to get run. I dont know how to send AttributeSet parameters. it's too confusing
package android.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.LinearGradient;
import android.graphics.Shader.TileMode;
import android.text.BoringLayout;
import android.util.AttributeSet;
import android.widget.TextView;
public class GradientTextView extends TextView {
public GradientTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
int mStartColor = 0;
int mEndColor = 0;
float mAngle;
String mText;
BoringLayout mLayout;
public GradientTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
int[] ids = new int[attrs.getAttributeCount()];
for (int i = 0; i < attrs.getAttributeCount(); i++) {
ids[i] = attrs.getAttributeNameResource(i);
}
TypedArray a = context.obtainStyledAttributes(attrs, ids, defStyle, 0);
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attrName = attrs.getAttributeName(i);
if (attrName == null)
continue;
if (attrName.equals("startColor")) {
mStartColor = a.getColor(i, -1);
}
else if (attrName.equals("endColor")) {
mEndColor = a.getColor(i, -1);
}
else if (attrName.equals("angle")) {
mAngle = a.getFloat(i, 0);
}
}
}
public static void setGradient(TextView tv, float angle, int startColor, int endColor) {
tv.measure(tv.getLayoutParams().width, tv.getLayoutParams().height);
LinearGradient gradient = getGradient(tv.getMeasuredWidth(), tv.getMeasuredHeight(), angle, startColor, endColor);
tv.getPaint().setShader(gradient);
}
static LinearGradient getGradient(int measuredWidth, int measuredHeight, float angle, int startColor, int endColor) {
// calculate a vector for this angle
double rad = Math.toRadians(angle);
double oa = Math.tan(rad);
double x;
double y;
if (oa == Double.POSITIVE_INFINITY) {
y = 1;
x = 0;
}
else if (oa == Double.NEGATIVE_INFINITY) {
y = -1;
x = 0;
}
else {
y = oa;
if (rad > Math.PI)
x = -1;
else
x = 1;
}
// using the vector, calculate the start and end points from the center of the box
int mx = measuredWidth;
int my = measuredHeight;
int cx = mx / 2;
int cy = my / 2;
double n;
if (x == 0) {
n = (double)cy / y;
}
else if (y == 0) {
n = (double)cx / x;
}
else {
n = (double)cy / y;
double n2 = (double)cx / x;
if (Math.abs(n2) < Math.abs(n))
n = n2;
}
int sx = (int)(cx - n * x);
int sy = (int)(cy - n * y);
int ex = (int)(cx + n * x);
int ey = (int)(cy + n * y);
return new LinearGradient(sx, sy, ex, ey, startColor, endColor, TileMode.CLAMP);
}
protected void onDraw(android.graphics.Canvas canvas) {
if (mGradient == null) {
mGradient = getGradient(getMeasuredWidth(), getMeasuredHeight(), mAngle, mStartColor, mEndColor);
getPaint().setShader(mGradient);
}
super.onDraw(canvas);
}
public int getStartColor() {
return mStartColor;
}
public void setStartColor(int startColor) {
mStartColor = startColor;
invalidate();
}
public int getEndColor() {
return mEndColor;
}
public void setEndColor(int endColor) {
mEndColor = endColor;
invalidate();
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mGradient = null;
}
LinearGradient mGradient;
}
This custom class are too confusing but they are used for tweaking like outline, gradients, and etc I am very keen to understand the flow
you can use this "View Element" by instanciating/using a view object:
TextView myView = new TextView (this);
myView.setText("Test");
GradientTextView.setGradient(myView, 90, Color.BLACK, Color.WHITE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
Layoutparams.WRAP_CONTENT);
myLayout.addView(myView, params);
You can even use this object using it your layouts XML file.