I would like to change a SeekBar's behaviour. Therefore I need to edit two methods in AbsSeekBar, one of them is protected the other is
How can I do it?
Now I can't simply copy said methods from the AbsSeekbar and override them in my custom SeekBar class - I end up missing all field parameters.
I also can't just edit the AbsSeekBar class itself, since it is not part of my project. Any help is appreciated.
These are the two methods:
public abstract class AbsSeekBar extends ProgressBar {
.
.
.
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!mIsUserSeekable || !isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isInScrollingContainer()) {
mTouchDownX = event.getX();
} else {
setPressed(true);
if (mThumb != null) {
invalidate(mThumb.getBounds()); // This may be within the padding region
}
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging) {
trackTouchEvent(event);
} else {
final float x = event.getX();
if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
setPressed(true);
if (mThumb != null) {
invalidate(mThumb.getBounds()); // This may be within the padding region
}
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsDragging) {
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
} else {
// Touch up when we never crossed the touch slop threshold should
// be interpreted as a tap-seek to that location.
onStartTrackingTouch();
trackTouchEvent(event);
onStopTrackingTouch();
}
// ProgressBar doesn't know to repaint the thumb drawable
// in its inactive state when the touch stops (because the
// value has not apparently changed)
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
onStopTrackingTouch();
setPressed(false);
}
invalidate(); // see above explanation
break;
}
return true;
private void trackTouchEvent(MotionEvent event) {
final int width = getWidth();
final int available = width - mPaddingLeft - mPaddingRight;
final int x = (int) event.getX();
float scale;
float progress = 0;
if (isLayoutRtl() && mMirrorForRtl) {
if (x > width - mPaddingRight) {
scale = 0.0f;
} else if (x < mPaddingLeft) {
scale = 1.0f;
} else {
scale = (float)(available - x + mPaddingLeft) / (float)available;
progress = mTouchProgressOffset;
}
} else {
if (x < mPaddingLeft) {
scale = 0.0f;
} else if (x > width - mPaddingRight) {
scale = 1.0f;
} else {
scale = (float)(x - mPaddingLeft) / (float)available;
progress = mTouchProgressOffset;
}
}
final int max = getMax();
progress += scale * max;
setHotspot(x, (int) event.getY());
setProgress((int) progress, true);
}
If you absolutely have to change how a private method works, you'll need to copy the entire class into your project, edit that copy, and use that version of the class instead of the original. Its something that should probably be avoided if possible.
I'm not sure I understand 'I end up missing all field parameters'
The only way you can edit a method's functionality in Java is by extending the class.
Example:
public class CustomSeekbar extends AbsSeekBar {
public CustomSeekbar(Context context) {
super(context);
}
#Override
protected boolean verifyDrawable(Drawable who) {
//Do your own method functionality
//Return your own true/false
return super.verifyDrawable(who);
}
}
In this case you don't have to return super.verifyDrawable if you don't want to, and you can supply your own response.
This is true for all protected or public methods in the AbsSeekBar class.
By reflection, you can modify fields of the class if you so please.
For example:
Field drawableField = absSeekBarInstance.getClass().getField("mThumb");
drawableField.setAccessible(true);
drawableField.set(absSeekBarInstance, someDrawable);
Or to GET the field values, you just say
drawableField.get(absSeekBarInstance);
To go even further, you'll need to provide me more information.
Private methods cannot be changed. It's just not possible.
Aspect-oriented programming in android
AspectJ comes close, but you can't do what you're describing. Thats just how Java works. In Objective-c you can do this, it is called 'swizzling'.
But yes, in Java, you cannot change the functionality of compiled methods.
Your best bet would be to copy AbsSeekBar and all of its required classes into your own project.
Related
I have been following this post in order to try and understand how Android can have similar functionality to iOS Jump List
Xamarin: Vertical alphabet Indexing (Jump List) for grouped list in Xamarin forms for Android and windows UWP
I have had no problem implementing an alphabetical index using layouts, and furthermore implemented scrollto so when selecting a letter with tap, the main list will act accordingly
However, a key usability difference is that iOS, with the letters in the above example being so small, is designed to let user simply keep thumb depressed on screen and move up and down
With Android, because we're 'mimicking' this using ListView, you only have Tapped and Selected
Neither of these gives the same fluid experience as the user has to lift finger on and off screen
Is there a way to extend the list view on Android only so user can, just like iOS does out of box, slide up and down the alphabetical list and send events to the device to process?
ItemTapped="AlphaView_ItemTapped"
ItemSelected="AlphaView_ItemTapped"
You could custom ListViewRenderer and SetOnTouchListener for listview, then can detect the hold & swip method.
For example, the sample renderer code as follows:
[assembly: ExportRenderer(typeof(Xamarin.Forms.ListView), typeof(CustomListViewRenderer))]
namespace XamarinForms20.Droid
{
public class CustomListViewRenderer : ListViewRenderer, Android.Views.View.IOnTouchListener
{
Context _context;
public CustomListViewRenderer(Context context) : base(context)
{
_context = context;
}
static float mPosX = 0, mPosY = 0, mCurPosX = 0, mCurPosY = 0;
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
// TODO Auto-generated method stub
int viewheight = v.Height;
switch (e.Action) {
case MotionEventActions.Down:
mPosX = e.GetX();
mPosY = e.GetY();
Console.WriteLine("Start on " + mPosY);
break;
case MotionEventActions.Move:
mCurPosX = e.GetX();
mCurPosY = e.GetY();
Console.WriteLine("Now is moving " + mCurPosY);
break;
case MotionEventActions.Up:
if (mCurPosY - mPosY > 0
&& (Math.Abs(mCurPosY - mPosY) > 25)) {
//scroll down
Console.WriteLine("scroll down to "+ mCurPosY/viewheight);
} else if (mCurPosY - mPosY< 0
&& (Math.Abs(mCurPosY - mPosY) > 25)) {
//scroll up
Console.WriteLine("scroll up to " + mCurPosY/viewheight);
}
break;
}
return true;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
Control.SetOnTouchListener(this);
}
}
}
}
Here is the full code of the finger paint app:
DrawingView: https://pastebin.com/4t8SUTDJ
MainActivity: https://pastebin.com/rU4nQwUS
MainActivity XML: https://pastebin.com/GLJjRty2
Stroke: https://pastebin.com/ihs03rEW
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
In my touch_up() method I am creating a new stroke object to add into an array, but for some reason setters for the class DO NOT WORK and I have no idea why it's not.
This DOES NOT work:
public DrawingView(Context context) {
super(context);
mUserStroke = new Stroke();
}
private void touch_up() {
mUserPath.lineTo(mUserX, mUserY);
mUserStroke.setPath(mUserPath);
mUserStroke.setPaint(mUserPaint);
mUserList.add(mUserStroke);
mUserPath = new Path();
}
However changing it to a constructor like so...:
private void touch_up() {
mUserPath.lineTo(mUserX, mUserY);
Stroke stroke = new Stroke(mUserPath, mUserPaint);
mUserList.add(stroke);
mUserPath = new Path();
}
works perfectly fine. I've been debugging for the entire day on this matter, but now I am beyond confused to why this is an issue.
Edit1:_______________________________________________________
This is the DrawingView using constructor:
https://pastebin.com/pZ26v3cg (it works),
but if you use setter methods instead:
https://pastebin.com/4t8SUTDJ
it DOES NOT work.
Here is the video of it: https://youtu.be/02VPZh73e8A
If you are using the same object in the list it will point to the same reference.
Changing an object will affect all items in the list.
In your case, all paths inside mUserList will have the same mNativePath.
If you want to keep references to all Paths, then you should initiate a new Stroke object when adding to the mUserList.
Here is a simplified example to understand better why is this happening:
TestObject item = new TestObject();
List items = new ArrayList();
items.add(item);
item.value = "Update 1";
items.add(item);
item.value = "Update 2";
items.add(item);
private class TestObject {
String value = "Default";
}
Results:
Many of us must have come across apps like Tinder and Dripper where you can pull down on the view containing an image and the image zooms in. And then when you let it go, the image zooms out to go back to its origin state.
Let's take an example from Tinder :
Original State: and Zoomed-in state when pulled:
In iOS it is done by
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"church-welcome.png"]];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.cachedImageViewSize = self.imageView.frame;
[self.tableView addSubview:self.imageView];
[self.tableView sendSubviewToBack:self.imageView];
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 170)];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat y = -scrollView.contentOffset.y;
if (y > 0) {
self.imageView.frame = CGRectMake(0, scrollView.contentOffset.y, self.cachedImageViewSize.size.width+y, self.cachedImageViewSize.size.height+y);
self.imageView.center = CGPointMake(self.view.center.x, self.imageView.center.y);
}
}
Since my expertise in Objective C and iOS is very limited, I am not being able to implement it in Android.
Here is what I think should be done :
catch the pull-down gesture
increase the height of the view by the pull amount
do some sort of scale animation on the Image to fit it in the expanded view
Does anyone have any idea if there is any library that could be used for this purpose?
Check out this project:
https://github.com/Gnod/ParallaxListView
If you combine it with the ViewPagerIndicator library, you pretty much get Tinder's profile page feature set
https://github.com/JakeWharton/Android-ViewPagerIndicator
I think the easiest way is to override the onTouchEvent method of View.
Something like this:
boolean inZoom = false;
float prevY = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventY = event.getY();
float eventX = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(touchedTheImage(eventX, eventY)){
setZoomCenter(eventX, eventY);
prevY = eventY;
inZoom = true;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if(inZoom){
changeZoomLevel(prevY, eventY);
return true;
}
break;
case MotionEvent.ACTION_UP:
if(inZoom){
resetZoomLevel();
inZoom = false;
return true;
}
break;
}
return false;
}
EDIT:
for the animation part consider this post:
https://stackoverflow.com/a/6650473/3568892
I have a Scroller is used for a fling. I initialize it with an initial velocity and some limits. When I later get the new Y value, getCurrY returns the Y limit passed into fling.
Here's heavily simplified code:
public class SimpleList extends ScrollView implements Runnable
{
private VelocityTracker velocityTracker = null; // initialization redacted
private Scroller scroller = null;
#Override
public boolean onTouchEvent(MotionEvent ev)
{
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
velocityTracker.addMovement (ev);
return true;
case MotionEvent.ACTION_MOVE:
{
velocityTracker.addMovement (ev);
return true;
}
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity (1000);
int index = ev.getActionIndex();
int pointerId = ev.getPointerId(index);
int velY = (int) VelocityTrackerCompat.getYVelocity (velocityTracker, pointerId);
velocityTracker.recycle();
scroller = new Scroller (getContext());
scroller.fling (0, 0, 0, velY,
Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE);
postDelayed (this, 100);
return true;
}
return false;
}
public void run()
{
if (scroller.isFinished())
return;
if (scroller.computeScrollOffset())
{
System.out.println ("curVel " + scroller.getCurrVelocity());
System.out.println ("newY " + scroller.getCurrY());
}
}
}
The initial velocity seems reasonable - around 1000 px/sec. The logged velocity is a bit less, which also seems reasonable. Yet, the "newY" value is the MAX_VALUE passed into fling.
Any ideas?
Any ideas?
One has to use OverScroller.
The documentation for Scroller wasn't clear to me - I thought that the min and max values were only used to limit the amount of scroll. However they are also used to determine the scroll range, meaning that computeScrollOffset uses the limits to scale the animation, not just to limit it.
In Android, View.onLongClickListener() takes about 1 sec to treat it as a long click. How can I configure the response time for a long click?
The default time out is defined by ViewConfiguration.getLongPressTimeout().
You can implement your own long press:
boolean mHasPerformedLongPress;
Runnable mPendingCheckForLongPress;
#Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
if (mPendingCheckForLongPress != null) {
v.removeCallbacks(mPendingCheckForLongPress);
}
// v.performClick();
}
break;
case MotionEvent.ACTION_DOWN:
if( mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new Runnable() {
public void run() {
//do your job
}
};
}
mHasPerformedLongPress = false;
v.postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= v.getWidth() + slop) ||
(y < 0 - slop) || (y >= v.getHeight() + slop)) {
if (mPendingCheckForLongPress != null) {
v. removeCallbacks(mPendingCheckForLongPress);
}
}
break;
default:
return false;
}
return false;
}
This may be late to answer but in case of someone needed.
You can change the time out if your phone is rooted.
simply set the value in database to desired time out value. This will effect system wide long press event.
directory: /data/data/com.android.providers.settings/databases
file : settings.db
table : secure
name : long_press_timeout
By coding ? you may need system app privilege. not so sure actually.
but if you back track the ViewConfiguration.getLongPressTimeout() method, you will see that the constant value is coming from Settings.Secure.LONG_PRESS_TIMEOUT which is a mapping for settings.db
time out set operation is written in View class so it effects every view.