I think probably I have the wrong idea here - can someone set me straight?
I have a label which when clicked causes a panel to appear - i did this with panel.visible = true - in fact it is pretty clunky though and I would love to have it slide open.
So I used a for-next loop to change the height of the panel dynamically, which I tried to slow up with a timer. But I'm doing something wrong:
Sub button_click
For i = 1 to 500
counter = i
timer1.initialize("timer1", 50)
timer1.enabled = true
next
End sub
sub timer1_click
panel.height = counter
timer1.enabled=false
end sub
This has the effect of a long delay and then the panel appears. Not quite what I was after. Does simply stating panel.height = xx cause the panel to be redrawn or do I have to use animation?
thanks....
You are initializing the timer 500 times by having it in the loop. This may cause the timer to excute immediately for 500 times instead of for the interval set. This, in turn, would not allow time for the panel to be redrawn. Even if it the code were right, an interval of 50 is 5/100ths of a second repeated 500 times is 25 seconds. That's a long time to sit and watch a panel go up.
However, even if you reduced the interval to 1, it could take about the same amount of time just to redraw the panel 500 times, depending on the device and the number of views on the panel. This means that you have to move more than 1 pixel at a time. To get the moving time down to a reasonable number of seconds, you could use an interval of 1 and move 5 pixels at a time, which works, but the movement is not very smooth. Also, the speed of movement can vary quite a bit on different devices, say from a 4" phone to a 10" tablet.
Sub Activity_Create
timer1.initialize("timer1", 1)
timer1.enabled = false
end sub
Sub button_click
counter1 = 0 ' counter1 should be DIMed in Sub Globals
timer1.enabled = true
end sub
Sub timer1_tick ' Note: not "time1_click"
counter1 = counter1 + 5
panel.Height = counter1
if counter1 = 500 then
timer1.enabled = false
end if
End Sub
You should really use an animation. You have a good example here :
Android Left to Right slide animation
You need to use Animation.
Below is the code which is used for sliding up/down animation
btn.setOnClickListener(new OnClickListener()
{
public void onClick(View arg0) {
isOpen=!isOpen;
if(isOpen)
{ //
lin1.getLayoutParams().height=actualHeight;
btn.setBackgroundResource(R.drawable.header_uparrow);
}
else
btn.setBackgroundResource(R.drawable.header_downarrow);
ani a=new ani();
a.setDuration(2000);
lin1.startAnimation(a);
}
//}
});
class ani extends Animation
{
public ani()
{
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
if(isOpen)
newHeight = (int)(initialHeight * interpolatedTime);
else
newHeight = (int)(initialHeight * (1-interpolatedTime));
lin1.getLayoutParams().height = newHeight;
lin1.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
initialHeight = actualHeight;
}
#Override
public boolean willChangeBounds() {
return true;
}
};
Related
I'm experimenting with this project:
https://github.com/maarek/android-wheel/blob/master/wheel-demo/src/kankan/wheel/demo/extended/PasswActivity.java
I've been trying to implement the following:
1) set the wheel to land on the number 5 (or any other number)
2) scroll the wheel for about 2 seconds
3) after spinning stops (could be any number of rotations through 0 to 9) the value shown is what's set in step 1
my work so far:
within WheelView -> WheelScroller I've added the following method (and also added a reference to the parent View):
public void flingToY(int initialVelocity, final int finalYindex) {
scroller.forceFinished(true);
lastScrollY = 0;
final int maxY = 0x7FFFFFFF;
final int minY = -maxY;
scroller.fling(0, lastScrollY, 0, initialVelocity, 0, 0, minY, maxY);
setNextMessage(MESSAGE_SCROLL);// specific to the view to allow it to scroll
parentView.postDelayed(new Runnable() {
#Override
public void run() {
//scroller.extendDuration(1000);
scroller.setFinalY(finalYindex);
}
}, 1000);
}
the end result:
after the call to setFinalY() the wheel just stops turning and the digit that's shown is always "0"
You can see I experimented with extendDuration() which does extend the spinning, but in the end the result is the same; the spinning wheel suddenly stops and the digit "0" is shown on the wheel.
need help:
How can I get a "WheelView" to spin through the numbers 0 .. 9 for a second and then slow down to a predetermined position ?
public void setCurrentItem(int index, boolean animated) method of WheelView will smoothly scroll to given item and then stop if you pass true for animated value.
I have implemented the method shown in this question and the animation on scroll looks perfectly fine. However, the initial list fill animation just shows all objects appear on the screen at the same time and it just looks like a lag.
While debugging, I can see that the animation method is being called 7 times, but I guess it is so fast that they are all trying to run at basically the same time. Any ideas what I can do? I tried delaying the animation, but I got stuck with how to do that. I asked that question here. Thank you for the help!
Edit: I can post the same code that I put on the other question:
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
//Normal OnBindViewHolder stuff
SetAnimation(vh.ItemView, position);
}
And then the SetAnimation method:
private void SetAnimation(View viewToAnimate, int position)
{
if (position > lastPosition)
{
var animation = AnimationUtils.LoadAnimation(_context, Resource.Animation.up_from_bottom);
//animation.SetAnimationListener(new CheckpointAnimationListener());
viewToAnimate.StartAnimation(animation);
lastPosition = position;
}
}
What I really want here is for the animation to finish before the lastPostion = position line is called.
And the empty AnimationListener, since I am really not sure how to handle the wait.
private class CheckpointAnimationListener : Java.Lang.Object, Animation.IAnimationListener
{
public void OnAnimationEnd(Animation animation)
{
}
public void OnAnimationRepeat(Animation animation)
{
}
public void OnAnimationStart(Animation animation)
{
}
}
I had to do something similar. Basically, the approach I took was whenever I started an animation, I also informed when the minimum start time would be, based on the last time the animation was initiated.
Since you didn't post any code, I'll write a basic outline of what I did:
// member to know when the minimum start time of the next animation will be.
private long mNextAnimationStartTime;
...
public void onBindViewHolder(...) {
// perform binding logic
// sample the current time.
long currentTime = System.currentTimeMillisec();
// if the next animation time is greater than now...
if (mNextAnimationStartTime > currentTime) {
// calculate how much time to wait before showing the next animation.
int delay = mNextAnimationStartTime - currentTime;
// In this example, I use postDelayed to postpone the animation,
// but you could also use start offset of the animation itself.
postDelayed(new Runnable() {
#Override
public void run() {
// start the animation after a delay.
startAnimation(viewToAnimate);
}
}, delay);
} else {
// if the next animation time is in the past, just start the animation.
startAnimation(viewToAnimate);
}
// schedule the next animation start time to now + 100 ms (play with this 100 to fit your needs)
mNextAnimationStartTime = currentTime + 100;
}
Hope this was clear, and helps you get started.
The answer from Gil sent me in the right direction, but since I am working with Xamarin.Android I needed to do a couple of things differently. I used his idea for StartOffset instead, since C# does not use anonymous classes (making runnables quite a bit more difficult). Here is my final animation code (after playing around with the delays).
private void SetAnimation(View viewToAnimate, int position)
{
var animation = AnimationUtils.LoadAnimation(_context, Resource.Animation.up_from_bottom);
_currentTime = JavaSystem.CurrentTimeMillis();
long difference = _currentTime - _timeAtFirstCall;
//this will cause the first items to populate the view to animate in order instead of at the same time.
//_delay is increased each time so each item will start 75ms after the one before it. difference is
//there to make sure later items in the list dont get this delay and animate as they appear.
if (_nextAnimationStartTime > _currentTime && difference < 150)
{
if (position > lastPosition)
{
animation.StartOffset = _delay;
viewToAnimate.StartAnimation(animation);
lastPosition = position;
_delay += 75;
}
}
else
{
if (position > lastPosition)
{
animation.StartOffset = 75;
viewToAnimate.StartAnimation(animation);
lastPosition = position;
}
}
_nextAnimationStartTime = _currentTime + 400;
}
And then I had these variables defined and initialized at the top of the class:
private int lastPosition = -1;
private long _nextAnimationStartTime = 0;
private long _currentTime = 0;
private long _timeAtFirstCall = JavaSystem.CurrentTimeMillis();
private long _delay = 150;
This method is called at the end of OnBindViewHolder. So now, the first time OnBindViewHolder is called it goes through the else statement. I had to set an initial delay here as well because otherwise the animation seemed to start half way on the screen and just looked bad. This delay also ensures the items will animate in sequence on a fast scroll.
The one problem that I still have is if the user scrolls immediately when loading the view. The initial items keep their delay, but the first item that is set to animate when it is scrolled to might start its animation before the first items are finished. I am not sure how to get around this problem...
I'm using LibGDX engine in my game and I started to use Tween for animation:
I have in my game a small 'custom' progress bar. One image is an empty progress bar and the second is full one, they booths have the same width and height.What I do is I update it by setting the position of the full as the same as the empty, and when I want to display progress I use:
private Image fullProgress; //its a libGDX image type
//I have already set the process
float originalHeight = progressEmpty.getHeight();
howMuchProgress = originalHeight * process;
fullProgress.setHeight(howMuchProgress);
Any ideas how can I make it change the height with delay animation, so I can see it for a few seconds? better will be to use the Tween Engine?
Given your code example, delay should already be calculated in the process.
If you need to visualize the delay just for the debugging purposes (for example, for faking the AssetManager's loading progress), I suggest you do something like this:
private static final float LOADING_MIN_TIME= 2.0f; // delay amount in seconds
private float loadingTimer;
#Override
public void render(float delta) {
// ...
loadingTimer += delta;
float loadingTimerScaled = loadingTimer / LOADING_MIN_TIME;
if (loadingTimerScaled >= 1.0f) {
loadingTimerScaled = 1.0f;
}
process = Math.min(process, loadingTimerScaled); // use the slow(fake) value.
}
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...
Goal
Build a Circular ViewPager.
The first element lets you peak to the last element and swipe to it, and vice versa. You should be able to swipe in either direction forever.
Now this has been accomplished before, but these questions do not work for my implementation. Here are a few for reference:
how to create circular viewpager?
ViewPager as a circular queue / wrapping
https://github.com/antonyt/InfiniteViewPager
How I Tried to Solve the Problem
We will use an array of size 7 as an example. The elements are as follows:
[0][1][2][3][4][5][6]
When you are at element 0, ViewPagers do not let you swipe left! How terrible :(. To get around this, I added 1 element to the front and end.
[0][1][2][3][4][5][6] // Original
[0][1][2][3][4][5][6][7][8] // New mapping
When the ViewPageAdapter asks for (instantiateItem()) element 0, we return element 7. When the ViewPageAdapter asks for element 8 we return element 1.
Likewise in the OnPageChangeListener in the ViewPager, when the onPageSelected is called with 0, we setCurrentItem(7), and when it's called with 8 we setCurrentItem(1).
This works.
The Problem
When you swipe to the left from 1 to 0, and we setCurrentItem(7), it will animate all the way to right by 6 full screens. This doesn't give the appearance of a circular ViewPager, it gives the appearence rushing to the last element in the opposite direction the user requested with their swipe motion!
This is very very jarring.
How I Tried to Solve This
My first inclination was to turn off smooth (ie, all) animations. It's a bit better, but it's now choppy when you move from the last element to the first and vice versa.
I then made my own Scroller.
http://developer.android.com/reference/android/widget/Scroller.html
What I found was that there is always 1 call to startScroll() when moving between elements, except when I move from 1 to 7 and 7 to 1.
The first call is the correct animation in direction and amount.
The second call is the animation that moves everything to the right by multiple pages.
This is where things got really tricky.
I thought the solution was to just skip the second animation. So I did. What happens is a smooth animation from 1 to 7 with 0 hiccups. Perfect! However, if you swipe, or even tap the screen, you are suddenly (with no animation) at element 6! If you had swiped from 7 to 1, you'll actually be at element 2. There is no call to setCurrentItem(2) or even a call to the OnPageChangeListener indicating that you arrived at 2 at any point in time.
But you're not actually at element 2, which is kind of good. You are still at element 1, but the view for element 2 will be shown. And then when you swipe to the left, you go to element 1. Even though you were really at element 1 already.. How about some code to help clear things up:
Animation is broken, but no weird side effects
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, duration);
}
Animation works! But everything is strange and scary...
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
if (dx > 480 || dx < -480) {
} else {
super.startScroll(startX, startY, dx, dy, duration);
}
}
The ONLY difference is that when the second animation (bigger than the width of the 480 pixel screen) is called, we ignore it.
After reading through the Android Source code for Scroller, I found that startScroll does not start scrolling anything. It sets up all the data to be scrolled, but doesn't initiate anything.
My Hunch
When you do the circular action (1 to 7 or 7 to 1), there are two calls to startScroll(). I think something in between the two calls is causing an issue.
User scrolls from element 1 to element 7 causing a jump from 0 to 7. This should animate to the left.
startScroll() is called indicating a short animation to the left.
STUFF HAPPENS THAT MAKES ME CRY PROBABLY I THINK
startScroll() is called indicating a long animation to the right.
Long animation to the right occurs.
If I comment out 4, then 5 becomes "Short correct animation to the left, things go crazy"
Summary
My implementation of a Circular ViewPager works, but the animation is broken. Upon trying to fix the animation, it breaks the functionality of the ViewPager. I am currently spinning my wheels trying to figure out how to make it work. Help me! :)
If anything is unclear please comment below and I will clarify. I realize I was not very precise with how things are broken. It's difficult to describe because it's not even clear what I'm seeing on the screen. If my explanation is an issue I can work on it, let me know!
Cheers,
Coltin
Code
This code is slightly modified to make it more readable on its own, though the functionality is identical to my current iteration of the code.
OnPageChangeListener.onPageSelected
#Override
public void onPageSelected(int _position) {
boolean animate = true;
if (_position < 1) {
// Swiping left past the first element, go to element (9 - 2)=7
setCurrentItem(getAdapter().getCount() - 2, animate);
} else if (_position >= getAdapter().getCount() - 1) {
// Swiping right past the last element
setCurrentItem(1, animate);
}
}
CircularScroller.startScroll
#Override
public void startScroll(int _startX, int _startY, int _dx, int _dy, int _duration) {
// 480 is the width of the screen
if (dx > 480 || dx < -480) {
// Doing nothing in this block shows the correct animation,
// but it causes the issues mentioned above
// Uncomment to do the big scroll!
// super.startScroll(_startX, _startY, _dx, _dy, _duration);
// lastDX was to attempt to reset the scroll to be the previous
// correct scroll distance; it had no effect
// super.startScroll(_startX, _startY, lastDx, _dy, _duration);
} else {
lastDx = _dx;
super.startScroll(_startX, _startY, _dx, _dy, _duration);
}
}
CircularViewPageAdapter.CircularViewPageAdapter
private static final int m_Length = 7; // For our example only
private static Context m_Context;
private boolean[] created = null; // Not the best practice..
public CircularViewPageAdapter(Context _context) {
m_Context = _context;
created = new boolean[m_Length];
for (int i = 0; i < m_Length; i++) {
// So that we do not create things multiple times
// I thought this was causing my issues, but it was not
created[i] = false;
}
}
CircularViewPageAdapter.getCount
#Override
public int getCount() {
return m_Length + 2;
}
CircularViewPageAdapter.instantiateItem
#Override
public Object instantiateItem(View _collection, int _position) {
int virtualPosition = getVirtualPosition(_position);
if (created[virtualPosition - 1]) {
return null;
}
TextView tv = new TextView(m_Context);
// The first view is element 1 with label 0! :)
tv.setText("Bonjour, merci! " + (virtualPosition - 1));
tv.setTextColor(Color.WHITE);
tv.setTextSize(30);
((ViewPager) _collection).addView(tv, 0);
return tv;
}
CircularViewPageAdapter.destroyItem
#Override
public void destroyItem(ViewGroup container, int position, Object view) {
ViewPager viewPager = (ViewPager) container;
// If the virtual distance is distance 2 away, it should be destroyed.
// If it's not intuitive why this is the case, please comment below
// and I will clarify
int virtualDistance = getVirtualDistance(viewPager.getCurrentItem(), getVirtualPosition(position));
if ((virtualDistance == 2) || ((m_Length - virtualDistance) == 2)) {
((ViewPager) container).removeView((View) view);
created[getVirtualPosition(position) - 1] = false;
}
}
I think the best doable approach would be instead of using a normal list to have a wrapper to the List that when the get(pos) method is executed to obtain the object to create the view, you make something like this get(pos % numberOfViews) and when it ask for the size of the List you put that the List is Integer.MAX_VALUE and you start your List in the middle of it so you can say that is mostly impossible to have an error, unless they actually swipe to the same side until the reach the end of the List. I will try to post a proof of concept later this weak if the time allows me to do so.
EDIT:
I have tried this piece of code, i know is a simple textbox shown on each view, but the fact is that it works perfectly, it might be slower depending on the total amount of views but the proof of concept is here. What i have done is that the MAX_NUMBER_VIEWS represents what is the maximum numbers of times a user can completely give before he is stopped. and as you can see i started the viewpager at the length of my array so that would be the second time it appears so you have one turn extra to the left and right but you can change it as you need it. I hope i do not get more negative points for a solution that in fact does work.
ACTIVITY:
pager = (ViewPager)findViewById(R.id.viewpager);
String[] articles = {"ARTICLE 1","ARTICLE 2","ARTICLE 3","ARTICLE 4"};
pager.setAdapter(new ViewPagerAdapter(this, articles));
pager.setCurrentItem(articles.length);
ADAPTER:
public class ViewPagerAdapter extends PagerAdapter {
private Context ctx;
private String[] articles;
private final int MAX_NUMBER_VIEWS = 3;
public ViewPagerAdapter(Context ctx, String[] articles) {
this.ctx = ctx;
this.articles = articles.clone();
}
#Override
public int getCount() {
return articles.length * this.MAX_NUMBER_VIEWS;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
TextView view = new TextView(ctx);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
int realPosition = position % articles.length;
view.setText(this.articles[realPosition]);
((ViewPager) container).addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public Parcelable saveState() {
return null;
}
}