I attempted to use a SeekBar for the first time, and I was disappointed with the results. I have an app that needs to use a slider to cover 80 spots. I want the user to be able to move in .5 increments (i.e. 10.5, 11.0). My max range is 40, which is why I doubled it to 80 to account for the decimals.
Everything worked, but when I put it on my Droid, the performance was terrible in my opinion. When trying to stop on 21.5, the slider usually stopped at 22.0 or 22.5, and I had to take extra time to try and inch it over. Other times I would try to select the slider and it was unresponsive. I'm not as concerned about the unresponsiveness, but I can not put the SeekBar in my app if it requires the user to have exact precision to hit a certain spot.
Is there anything that I can do to adjust the SeekBar so that it is easy to hit the number you are trying to land on without difficulty? I'm not sure if the problem is occurring because my SeekBar contains a large amount of possible values, and with screen size limited this forces the numbers to be smashed closer together? Or if this is just the way it behaves in general.
If there isn't a good way to adjust the sensitivity, is there a good alternative that I can use that provides similar functionality?
I have the exact same problem and came up with my own way of reducing the sensitivity. The problem for me happens when I am leaving the slider and it unintentionally gets repositioned, because of thickness of fingers etc. So I wrote a timer to see if the position was selected for a while and snap to this position while leaving the seekbar. This method is a bit clumsy but this is the best I can think of, given I do not want +/- buttons as suggested above. It works well for my needs. Here is the code for anyone interested:
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
int lastProgress = 0;
Timer endTimer = null;
boolean timerExpired = false;
class SeekBarTimerTask extends TimerTask {
public void run() {
timerExpired = true;
}
}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(timerExpired == false) {
// clean up the previous timer and start a new one
setTimer();
}
else {
// if the timer has expired, do not honor small variations of 2%
if(Math.abs(progress - lastProgress) <= seekBar.getMax() * 0.02) {
seekBar.setProgress(lastProgress);
return;
}
else {
// The variation is not small. This means that the seekbar position change
// was intentional. Honor it and schedule the timer again.
setTimer();
}
}
lastProgress = progress;
}
public void onStartTrackingTouch (SeekBar seekBar) {}
public void onStopTrackingTouch (SeekBar seekBar) {
timerExpired = false;
endTimer.cancel();
endTimer.purge();
}
private void setTimer() {
if(endTimer != null) {
endTimer.cancel();
endTimer.purge();
}
timerExpired = false;
endTimer = new Timer();
endTimer.schedule(new SeekBarTimerTask(), 300);
}
});
I'm not sure if the problem is
occurring because my SeekBar contains
a large amount of possible values, and
with screen size limited this forces
the numbers to be smashed closer
together?
Pretty much this. One thing you could do is add a small plus and minus button beside the seekbar, which increment and decrement the value by 0.5. This way you could still use the seekbar for general cases, and refine your selection with the buttons if needed. Maybe not the most elegant solution, but it would certainly work.
Related
i'm designing a automation for android by UiAutomatorTestCase class.
I want to control SeekBar swiping and TestView on UIAutomator.
for instance, if SeekBar is moved to left. TextView is changed negative number. so if the number is "40". I want to stop the swiping.
I could swipe the seekbar to left direction but can't stop it.
UiScrollable seekbar = new UiScrollable(new UiSelector().resourceId("com.test:id/setting_item_child_menu_seekbar"));
seekbar.swipeLeft(200);
please help me how can I stop that SeekBar when some test of TextView is "40".
I found alternative way to control seekbar, since I also found the standard methods hard to use because of some problems, so here it is:
private void clickSeekbar (float position) {
while ((seekBarUiObj2 = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE+":id/seekbar"))) == null) {
mDevice.click(200,200);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Rect rect = seekBarUiObj2.getVisibleBounds();
mDevice.click(rect.left+5/*pixels*/+(int)((rect.right-rect.left-10/*pixels*/)*position), ((rect.top+rect.bottom)/2));
}
The trick is to click on the device on the desired location on the seekbar.
It works.
The whlle loop makes sure the seekbar is on screen, if not tap the screen on some location, which makes seekbar to appear.
Then calculate the location to be clicked according to desired position.
Only if your seekbar is very thin like 1 or 2 pixels it might not work.
I'm having trouble using a Scroller to scroll a ScrollView programmatically, so no touch gestures are involved in this so far. I want to scroll the ScrollView down at a certain speed, as long as data from a sensor is in a certain range. So basically I want to start scrolling the first time the data enters the valid range and then not disturb the scrolling process until the data is out of the range again. I don't want to connect the onSensorChanged directly to a scrollBy() because it will probably not work right on other devices. Here's what I've got so far:
in my onCreate:
tx = new TextView(ShowLyrics.this);
mainscrollarea = (ScrollView) findViewById (R.id.mainscrollarea);
scroller = new Scroller(getApplicationContext(), new LinearInterpolator());
tx.setScroller(scroller);
in my onSensorChanged:
if(integratedAngle - scrollTolerance > pointzero){ //this is checking for the data range and works so far
if(!scrollingDown){
scrollText("down");
}
}
and the scrollText function:
void scrollText(String direction){
if(direction.matches("down")){
scrollingUp = false;
scrollingDown = true;
scroller.forceFinished(true);
int currY = mainscrollarea.getScrollY();
int endY = tx.getHeight();
int distance = endY - currY;
scroller.startScroll(0, currY, 0, -distance, 5000);
}
if(direction.matches("up")){
//nothing yet
}
}
So for now I've hardcoded 5 seconds for a scroll down, but nothing happens. A Log.d() of the Scroller's getCurrY in the onSensorChanged only spits out 0's. If someone could point me in the right direction, I would be thankful.
I kind of do an automated scrolling like you. Except I rely on user input (when the user is near the edge of the screen with his finger, I start scrolling at a specific speed).
I use a runnable which does the same as the scroller will do.
private final DragScroller mDragScroller;
/** inner class */
private class DragScroller implements Runnable {
private SCROLL_DIRECTION mDirection;
private boolean mIsFinished = true;
DragScroller(Context context) {
}
void start(SCROLL_DIRECTION direction) {
mState = STATE.DRAG_SCROLL;
mIsFinished = false;
mDirection = direction;
post(this);
}
#Override
public void run() {
if (mIsFinished) {
return;
}
if (mDirection.equals(SCROLL_DIRECTION.UP)) {
// check if the touch is still in the correct area...
if (!isOverThreshold(0, mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() - mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
} else {
// check if the touch is still in the correct area...
if (!isOverThreshold(getHeight(), mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() + mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
}
}
public boolean isFinished() {
return mIsFinished;
}
public void forceFinish() {
mIsFinished = true;
}
}
It is simply started by: mDragScroller.start(SCROLL_DIRECTION.UP); and can be stopped by mDragScroller.forceFinish();
edit
Based on your comment, you want to use the duration for the speed. This is kind of problematic because the resulting speed of the scroll depends on the distance you have to scroll in your given time. Short math sample: Scrolling 600px in 1 minute means you scroll 10px per second which is not that bad (depends on what you scroll, text or image...) but if you are near the edge and you need to scroll only 60px, the resulting speed depending on the given duration of 1min means very slow 1px per second.
Given that example you should base your scroll speed not on total duration but on pixel per second.
And yes, there is no need to use a Scroller for programmatically scrolling. Just the runnable which will call itself until it should stop and the speed can be adjusted to what ever you need...
I have been working on a ListViewidea where it keeps scrolling automatically with no user interaction and that is absolutely doable using the android APIs for instance smoothScrollToPositionFromTop.
I have implemented ListView BaseAdapter where it load items forever (almost) to get a non stopping self repeated ListView.
What I want to achieve here is to keep myListViewscrolling forever with certain speed (slow) to make items clear and readable while scrolling down, I not sure yet if ListView is my best choice here.
below is a snippet of what I am trying to do. the result is good somehow but it's not smooth enough, I can feel the ListView flickers.
I need to improve smoothness, efficiency and control the speed
new Thread(new Runnable() {
#Override
public void run() {
int listViewSize = mListView.getAdapter().getCount();
for (int index = 0; index < listViewSize ; index++) {
mListView.smoothScrollToPositionFromTop(mListViewA.getLastVisiblePosition() + 100, 0, 6000);
try {
// it helps scrolling to stay smooth as possible (by experiment)
Thread.sleep(60);
} catch (InterruptedException e) {
}
}
}
}).start();
I suggest, thath your adapter implemented in effective way.
so this code is just scrolls listview
you need to try another values of variables
final long totalScrollTime = Long.MAX_VALUE; //total scroll time. I think that 300 000 000 years is close enouth to infinity. if not enought you can restart timer in onFinish()
final int scrollPeriod = 20; // every 20 ms scoll will happened. smaller values for smoother
final int heightToScroll = 20; // will be scrolled to 20 px every time. smaller values for smoother scrolling
listView.post(new Runnable() {
#Override
public void run() {
new CountDownTimer(totalScrollTime, scrollPeriod ) {
public void onTick(long millisUntilFinished) {
listView.scrollBy(0, heightToScroll);
}
public void onFinish() {
//you can add code for restarting timer here
}
}.start();
}
});
Here a few pointers : Simulate onFling() programmatically instead of detecting it (Android)
and Programmatically Fling ListView Android
It's hard to figure out what you call smooth enough in your case. Usually smoothness problems are related to a non optimal usage of listviews and troubles in either cell layouts or view creation / recycling inside the getView method of adapters.
Do you use a placeholder ?
An important thing to consider is also Drawables usage.
I never achieved what you are looking for, but a simple idea that comes to mind is :
find a way to scroll the view of 1 position or 2.
use a ring buffer inside your adapter. For instance let's say you got 100 items in your list of items. Then at the beginning, item 0 of the listview is item 0 of your list. When listview is scrolled up of 1 item, then item 0 of listview should become item 1 in your list. Thus the problem would not be scrolling but more syncing with scrolling and displaying an endless list of items.
My app scrolling is super fast!
How can I limit the scroll speed of a scroll view in my android app?
The scroll can be very fast and it's meaningless to scroll in that speed.
This thread is old, but I will reply with a partial solution: limiting the fling velocity. Feel free to comment so I can improve my solution.
As explained in the Developer Training guide:
Flinging is the type of scrolling that occurs when a user drags and lifts her finger quickly.
That's where I needed a velocity limit. So, in the Custom ScrollView (whether horizontal or vertical)
override fling method like this.
#Override
public void fling(int velocityY) {
int topVelocityY = (int) ((Math.min(Math.abs(velocityY), MAX_SCROLL_SPEED) ) * Math.signum(velocityY));
super.fling(topVelocityY);
}
I found that velocityY (in horizontal scrollview, it would be velocityX) could be between -16000 and 16000. Negative just means scrolling back. I'm still testing this values, and I have tested it in only one device. Not sure if it's the same in older devices/API versions. I will come back later to edit this.
(int) ((Math.min(Math.abs(velocityY), MAX_SCROLL_SPEED) ) * Math.signum(velocityY));
What I'm doing there is obtaining the minimum value between my constant MAX_SCROLL_SPEED and original velocityY, then obtaining the sign of the original velocityY. We need the sign to scroll back.
Finally, sending back the modified velocityY.
It's a partial solution, because if the user keeps pressing the scrollview, the speed won't change.
Again, feel free to improve my answer, I'm still learning.
ObjectAnimator anim = ObjectAnimator.ofInt(mScrollView, "scrollY", mScrollView.getBottom());
anim.setDuration(9000);
anim.start();
I think using timer you can limit the speed of scroll. look at this link Android: HorizontalScrollView smoothScroll animation time
This is how I achieved a smooth vertical scroll (like movie credits). This also allows the user to move the scroll up and down and allow it to continue scrolling when they let go. In my XML, I encapsulated my TextView inside of a ScrollView called "scrollView1". Enjoy!
final TextView tv=(TextView)findViewById(R.id.lyrics);
final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView1);
Button start = (Button) findViewById(R.id.button_start);
Button stop = (Button) findViewById(R.id.button_stop);
final Handler timerHandler = new Handler();
final Runnable timerRunnable = new Runnable() {
#Override
public void run() {
scrollView.smoothScrollBy(0,5); // 5 is how many pixels you want it to scroll vertically by
timerHandler.postDelayed(this, 10); // 10 is how many milliseconds you want this thread to run
}
};
start.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
timerHandler.postDelayed(timerRunnable, 0);
}
});
stop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
timerHandler.removeCallbacks(timerRunnable);
}
});
I have a Gallery widget set up to auto advance every 10000ms. However the transition between views is instantaneous and I would prefer to have a transition.
My advancing method is like so:
private void tickerAdvance() {
int selectedItemPosition = mTickerGallery.getSelectedItemPosition();
if (selectedItemPosition + 1 == mTickerGallery.getCount()) {
mTickerGallery.setSelection(0, true);
} else {
mTickerGallery.setSelection(selectedItemPosition + 1, true);
}
}
I was under the impression that setting the animation to true would cause it to animate between states. In my XML I've also added animationDuration="500", however the transition still pops between states instantly.
The problem is because you have used the setSelection() which obviously won't give you the feel of an Gallery being scrolled. SO instead of using setSelection() you have to override the onScroll() of gallery.
Here is how you do it.
Assuming that you have made the necessary steps for auto advancing periodically, now do this code.
MotionEvent e1, e2;
gallery.onScroll(e1, e2, scroll_limit , 0); //scroll limit is your integer variable for scroll limit.
The answer I came up with is that the Gallery API is terribly non-extensible. There are multiple package private or private methods that would unlock this functionality without a kludge including moveNext, FlingRunnable.startwithdistance, and scrollToChild. Alas, they are all unavailable.
Instead the best answer I was able to come up with was to override setSelection to get all the behaviors I needed. In the setSelection method I reverse the deceleration algorithm to calculate a velocity for a calculated distance and then call onFling. This is a nasty kludge could be made a ton better by making any of the above methods protected or public (I can't fathom a reason why at least moveNext and scrollToChild shouldn't be).
i had a similar task to make item to item animation and i decided to choose gallery, so i tried a lot of things, but the best way is to subclass gallery and add this:
boolean avoidSound = false;
public void showNext() {
avoidSound = true;
this.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, null);
}
public void playSoundEffect(int soundConstant) {
if (avoidSound) {
avoidSound = false;
} else {
super.playSoundEffect(soundConstant);
}
}
just call showNext() and it will do an animation