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);
}
Related
I have a layout where a WebView is inside a ScrollView, and I want to be able to switch between these two to decide which one receives the touch events. Unfortunately no matter what I do, the ScrollView seems to steal the touch events from the WebView, making it impossible to pan & zoom smoothly in the WebView. Is there some solution for this?
I have tried ScrollView.SetOnTouchListener(); and set a listener that returns true, this stops the ScrollView from scrolling but does not prevent the touch events being intercepted before they reach the WebView.
I have also tried WebView.Parent.RequestDisallowInterceptTouchEvent(true); and WebView.Parent.RequestDisallowInterceptTouchEvent(true); which both seem to have no effect.
Try using NestedScrollView and implement NestedScrollingChild to a custom WebView.
Reference Link
and some code
public class NestedWebView extends WebView implements NestedScrollingChild {
private int mLastY;
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private int mNestedOffsetY;
private NestedScrollingChildHelper mChildHelper;
public NestedWebView(Context context) {
this(context, null);
}
public NestedWebView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.webViewStyle);
}
public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
boolean returnValue = false;
MotionEvent event = MotionEvent.obtain(ev);
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsetY = 0;
}
int eventY = (int) event.getY();
event.offsetLocation(0, mNestedOffsetY);
switch (action) {
case MotionEvent.ACTION_MOVE:
int deltaY = mLastY - eventY;
// NestedPreScroll
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1];
mLastY = eventY - mScrollOffset[1];
event.offsetLocation(0, -mScrollOffset[1]);
mNestedOffsetY += mScrollOffset[1];
}
returnValue = super.onTouchEvent(event);
// NestedScroll
if (dispatchNestedScroll(0, mScrollOffset[1], 0, deltaY, mScrollOffset)) {
event.offsetLocation(0, mScrollOffset[1]);
mNestedOffsetY += mScrollOffset[1];
mLastY -= mScrollOffset[1];
}
break;
case MotionEvent.ACTION_DOWN:
returnValue = super.onTouchEvent(event);
mLastY = eventY;
// start NestedScroll
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
returnValue = super.onTouchEvent(event);
// end NestedScroll
stopNestedScroll();
break;
}
return returnValue;
}
// Nested Scroll implements
#Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
#Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
#Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
#Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
#Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
#Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
#Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
#Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
Declare NestedWebView instead of declaring WebView inside the NestedScrollView.For example
<com.nestedscrollwebviewexample.NestedWebView
android:id="#+id/nested_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:fillViewport="true"
android:focusable="true"
android:isScrollContainer="false"
android:visibility="visible"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
Instead of declaring Webview you can initialize as NestedWebView inside your Activity
private NestedWebView mShopWebView;
mShopWebView = (NestedWebView) findViewById(R.id.url_load_webview);
Hope this serves the purpose.
I worked around this by adding a floating WebView over the screen at the same level of the hierarchy as the ScrollView and linked its position to the scroll value, then switched touch control with WebView.BringToFront and ScrollView.BringToFront
I have a number of buttons in a LinearLayout.
The buttons have 1 weight, so they are placed evenly into the layout.
I want to do something like this
for (int i = 0; i < texts.size(); ++i) {
Button button = new Button(/*context*/);
button.setLayoutParams(/*WRAP_CONTENT, MATCH_PARENT, 1.0f*/)
button.setText(texts[i]);
float fontSize = 20.0f;
button.setTextSize();
while (textDoesNotFitIntoButton(button)) {
fontSize -= 2.0f;
button.setTextSize(fontSize);
}
linearLayout.addView(button);
}
How do I check if text does not fit into the button?
The point is to fill all the button with text in such a way, that text occupies all available space and gets neither truncated nor split into lines.
E.g.
String[] texts = new String[] { "0", "sin^-1" };
If I choose a single text size for "0" to occupy the whole space then I either get "sin" split into two lines "sin^" and "-1" or "0" appears smaller than it should.
I have edited the answer given by #Jaswanth Manigundan to make it extend from android.support.v7.widget.AppCompatButton. Have a look at the below scripts:
FontFitTextView.java
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
public class FontFitTextView extends android.support.v7.widget.AppCompatButton
{
private static final float THRESHOLD = 0.5f;
private enum Mode { Width, Height, Both, None }
private int minTextSize = 1;
private int maxTextSize = 50;
private Mode mode = Mode.None;
private boolean inComputation;
private int widthMeasureSpec;
private int heightMeasureSpec;
public FontFitTextView(Context context) {
super(context);
}
public FontFitTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
tAttrs.recycle();
}
private void resizeText() {
if (getWidth() <= 0 || getHeight() <= 0)
return;
if(mode == Mode.None)
return;
final int targetWidth = getWidth();
final int targetHeight = getHeight();
inComputation = true;
float higherSize = maxTextSize;
float lowerSize = minTextSize;
float textSize = getTextSize();
while(higherSize - lowerSize > THRESHOLD) {
textSize = (higherSize + lowerSize) / 2;
if (isTooBig(textSize, targetWidth, targetHeight)) {
higherSize = textSize;
} else {
lowerSize = textSize;
}
}
setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
measure(widthMeasureSpec, heightMeasureSpec);
inComputation = false;
}
private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
measure(0, 0);
if(mode == Mode.Both)
return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
if(mode == Mode.Width)
return getMeasuredWidth() >= targetWidth;
else
return getMeasuredHeight() >= targetHeight;
}
private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
return Mode.Both;
if(widthMode == MeasureSpec.EXACTLY)
return Mode.Width;
if(heightMode == MeasureSpec.EXACTLY)
return Mode.Height;
return Mode.None;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!inComputation) {
this.widthMeasureSpec = widthMeasureSpec;
this.heightMeasureSpec = heightMeasureSpec;
mode = getMode(widthMeasureSpec, heightMeasureSpec);
resizeText();
}
}
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
resizeText();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh)
resizeText();
}
public int getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
resizeText();
}
public int getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int maxTextSize) {
this.maxTextSize = maxTextSize;
resizeText();
}
}
style.xml:
<resources>
<declare-styleable name="FontFitTextView">
<attr name="minTextSize" format="dimension" />
<attr name="maxTextSize" format="dimension" />
</declare-styleable>
</resources>
And in my activity_main.xml, I used the FontFitTextView tag instead of Button:
<com.example.myname.testsidebar.FontFitTextView
android:id="#+id/userDetailsBtn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:drawableTop="#drawable/user_details"
android:gravity="center"
android:background="#00FFFFFF"
android:paddingTop="10sp"
android:textSize="15sp"
android:text="Your Details"
android:drawableTint="#ED1B2F"
android:textColor="#797369"
android:layout_weight="1"
android:layout_gravity="center"/>
Hope it helps.
i made custom Image View, where a simple line is drawn. This drawing is triggeed by a button.
When triggered the onClicklistener isnt working anymore. But on the screen the button remains clickable, the buttons changes to a blueish color when clicked (default Android button). I think the problem might be in the "drawLine()" when the "setContentView(R.layout.activity_main);" is called, but not sure why and how to get rid of it. Hope you can help.
MainActivity.java
public class MainActivity extends ActionBarActivity implements OnTouchListener,
OnClickListener {
private int number;
private Handler handler;
private boolean Running = true;
private int endX = 50;
private int endY = 500;
private int startX = 50;
private int startY = 50;
private int frames = 25;
ImageView Line01;
Button buttonLineDrawer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Line01 = (ImageView) findViewById(R.id.Line01);
Line01.setVisibility(View.GONE);
Button buttonLineDrawer;
buttonLineDrawer = (Button) findViewById(R.id.buttonLineDrawer);
buttonLineDrawer.setOnClickListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "klick", Toast.LENGTH_LONG)
.show();
Running = true;
number = 0;
drawLine();
}
public void drawLine() {
Line01.setVisibility(View.VISIBLE);
handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
while (Running) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
int coordX = ((((endX - startX) / frames) * number))
+ startX;
int coordY = ((((endY - startY) / frames) * number))
+ startY;
number += 1;
CustomDraw.setCoordinates(startX, startY, coordX,
coordY);
setContentView(R.layout.activity_main);
if ((coordX - endX) == 0 && coordY - endY == 0) {
Running = false;
}
}
});
}
}
};
new Thread(runnable).start();
}}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF8800"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="de.trialar.linedrawer.MainActivity" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="#+id/buttonLineDrawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<de.trialar.linedrawer.CustomDraw
android:id="#+id/Line01"
android:layout_width="400dp"
android:layout_height="400dp" />
</LinearLayout></RelativeLayout>
CustomDraw.java
public class CustomDraw extends ImageView {
Paint paint = new Paint();
private static int endX= 500;
private static int endY= 500;
private static int startX= 50;
private static int startY= 50;
static Context context;
public CustomDraw(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
}
public CustomDraw(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDraw(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
canvas.drawLine(startX, startY, endX, endY, paint);
}
public static void setCoordinates(int startX, int startY, int endX, int endY) {
System.out.println("SetCoordinates");
CustomDraw.endX = endX;
CustomDraw.endY = endY;
CustomDraw.startX = startX;
CustomDraw.startY = startY;
}}
i think i found the problem: when you start the handler.run() you set a new Layout:
public void run() {
int coordX = ((((endX - startX) / frames) * number)) + startX;
int coordY = ((((endY - startY) / frames) * number)) + startY;
number += 1;
CustomDraw.setCoordinates(startX, startY, coordX, coordY);
//maybe this is wrong
setContentView(R.layout.activity_main);
//try instead:
CustomDraw.invalidate();
if ((coordX - endX) == 0 && coordY - endY == 0) {
Running = false;
}
}
i don't get why you set a new layout... i guess you want to repaint the button... that's why i added a 'repaint'-line in that code-piece above
I searched the topic for several days, but finally confused. The idea is to create own alarm app with some special tricks. Firstly, I need clock hands. For creating custom clock, I used own class, which extends View. Hour and minute hands are PNG images.
They should be located in the center of the screen, but they dont. Actually, I can't even see them. And that is the question.
Here is the Clock class
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
//import android.os.Handler;
import android.text.format.Time;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
#SuppressLint("NewApi")
public class Clock extends View {
public Clock(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
private Drawable mHourHand;
private Drawable mMinuteHand;
private boolean mAttached;
static private float mMinutes;
static private float mHour;
private boolean mChanged;
Context mContext;
private boolean mSeconds;
// Gettes & setters. These clock must present alarm time which user sets in the next view
protected float getmMinutes() {
return mMinutes;
}
protected static void setmMinutes(float mMinutes) {
Clock.mMinutes = mMinutes;
}
protected float getmHour() {
return mHour;
}
protected static void setmHour(float mHour) {
Clock.mHour = mHour;
}
private Point size;
// ctors
public Clock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Clock(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
Resources r = context.getResources();
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.AnalogClock, defStyle, 0);
mContext=context;
mHourHand = r.getDrawable(R.drawable.hours);
mMinuteHand = r.getDrawable(R.drawable.minuts);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
// getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 150; // and yes, 150 what? px, inches, dpi-s? I draw it just randomly
int desiredHeight = 150;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
}
#SuppressWarnings({ "deprecation" })
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
boolean changed = mChanged;
if (changed) {
mChanged = false;
}
boolean seconds = mSeconds;
if (seconds ) {
mSeconds = false;
}
int w = 100; //These are too made randomly
int h = 100;
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
// if (android.os.Build.VERSION.SDK_INT <= 13)
// {
w = display.getWidth(); // deprecated
h = display.getHeight(); // deprecated
//}
// else if (android.os.Build.VERSION.SDK_INT > 13)
//{
//size = null;
//display.getSize(size);
//w = size.x;
//h = size.y;
//} ... I cant figure out, why, but size returns null. So I'll use deprecated ones just for
now.
// **Here are my measures. I suggest that if height of an hour hand should be about 1/4 of
screen width, then following the proportion - width of that hand should be old width*new
height/old height, or smthng**
int sizeXHour = w/3;
int sizeYHour = mHourHand.getIntrinsicHeight()*sizeXHour/mHourHand.getIntrinsicWidth();
int xHour = sizeXHour/2;
int yHour = sizeYHour/2;
canvas.rotate(mHour / 12.0f * 360.0f, xHour, yHour);
final Drawable hourHand = mHourHand;
if (changed) {
hourHand.setBounds((w / 2) - xHour, (h / 2) - yHour, sizeXHour, sizeYHour);
}
hourHand.draw(canvas);
canvas.restore();
int sizeYMinute = h/4;
int sizeXMinute = mMinuteHand.getIntrinsicWidth()*sizeYMinute/mMinuteHand.getIntrinsicHeight();
int xMinute = sizeXMinute/2;
int yMinute = sizeYMinute/2;
canvas.save();
canvas.rotate(mMinutes / 60.0f * 360.0f, xMinute, yMinute);
final Drawable minuteHand = mMinuteHand;
if (changed) {
minuteHand.setBounds((w / 2 - xMinute), (h / 2 - yMinute), sizeXMinute, sizeYMinute);
}
minuteHand.draw(canvas);
canvas.restore();
canvas.save();
}
I feel that something is very wrong, but can not figure what. The XML is:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="ee.st.running.dreamyclock.MainActivity"
android:background = "#drawable/clockk" >
<View
android:id="#+id/view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<st.running.dreamyclock.Clock
android:id="#+id/clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical = "true"
android:layout_marginTop="124dp" />
</RelativeLayout>
the attrs are:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AnalogClock">
<attr name="hand_hour" format="reference"/>
<attr name="hand_minute" format="reference"/>
</declare-styleable>
</resources>
Any ideas where it came from? Thank you
Was this ever working? Try to add this to Clock constructor:
setWillNotDraw(false);
"If this view doesn't do any drawing on its own, set this flag to allow further optimizations. By default, this flag is not set on View, but could be set on some View subclasses such as ViewGroup. Typically, if you override onDraw(android.graphics.Canvas) you should clear this flag."
Edit:
If you want to re draw your view, you should call invalidate() method
"Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. "
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
this.invalidate();
}
You don't even have to call invalidate(); if you just want to for example rotate the view:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
setRotation(20);
}
Edit 2:
When you are setting bounds for a Drawable, the "right" value should be higher than "left" for example:
minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2) + 4, y + (h / 4));
or
minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2) + minuteHand.getIntrinsicWidth(), y + (h / 4));
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..