I have a tablelayout for a calendar with 300 cells. When the user touches dates in a flow/move touch e.x. 6 days, it sets a background drawable.
The drawable is set with XML (shape, rect, ...). It iterates only over this 6 days, not over all cells.
The Problem is, that on Nexus 7 and older devices setting a backgroundDrawable consumes too much time. When the finger is on the 6th day, it sets the 4th day till it also arrives the last day!!!
How can I improve it ?
Flow: onDown(...), onMove(...) over 6 days, onUp(...).
EDIT:
public boolean onMove(MotionEvent e) {
Day selectedDay = searchForSelectedDay((int) e.getX(), (int) (e.getY() + getScrollY()));
if (selectedDay != null && selectedDay != mPreviousDay){
defineDayContent(mPreviousDay, selectedDay);
}
...
defineDayContent(Day arg0, Day arg1){
//iterates from arg0 till arg1 and sets the background
for(Day d : (iterate from arg0 till arg1)){
d.setBackground(Calendar.BACKGROUND_DRAWABLE);
}
}
You provided no code, that's why it's difficult to say where exactly the bottleneck is. I would try following measures.
As the first measure I would preload drawables making sure I don't call getDrawable() or setBackgroundResource() inside onTouch() method.
Secondly, I will make sure I don't call setBackground() in onTouch() for a view twice or multiple time, if background has already been set.
If first two don't help, I would call setBackground() delayed, only after a user slows his/her finger down over a certain view. This will avoid visual effect you described and speed up updates.
This was not solved, but ...
We tried to inflate views and set visibility of imageviews. Very quick, but the inflating of xmls for 300 cells was to heavy... 2-3 seconds to init.
We just decided to draw the content of the cells. That is very fast in layouting + in performance.
Solution at the end: CustomView + onDraw
yayyy
Related
I have implemented scroll view and I need to detect the visibility of the view when scrolling and trigger that event once per screen initialization.
But, at random times, I get it triggered multiple times in a very short time (a few ms), and it messes up with my logic in VM.
Is there a way to restrict this triggering and make it trigger once every 100ms or something similar?
An easy way to do this is something like this right where the trigger happens
if (System.currentTimeMillis() - lastTime < 100)
return
else {
lastTime = System.currentTimeMillis()
// rest of your code
}
where lastTime is just some variable you keep in your class
I'm testing my app but have a problem with the DatePicker.
All I need is to swipe down until another month appears (searching for a specific date).
The structure is a bit tricky but I made it work using
new UiScrollable(new UiSelector().className(android.widget.ViewAnimator.class.getName())).scrollTextIntoView("November")
Problem is, even though the view is scrolling, it is
1) scrolling in the wrong direction (up)
2) not stopping even though a November has already passed.(even November 2017 or November 2015...)
How can I create this condition. After all the views are named in a unique way so checking the structure would be possible finding "01 November 2016".
Okay, looks like I found a solution that works more or less fine:
At first I tried to use the sub conditions since UiSelector might have a child definition. But that proved hard since inside of the ViewAnimator is a (single) child object ListView containing the "calendar month" view.
So finding a childview in there with the description "15 December..." was tricky.
The new solution does this.
while (tries < MAX_TRIES)
if element with description "15 December ..." exists
-> click and exit loop
scroll down 1 element (next month)
-> tries++
I repeat this until the element is found (and clicked) or the maximum scroll tries are exhausted.
After scrolling I let the device wait for 1 second. This is useful since swiping is called asynchronously and would continue swiping for a moment while processing the click.
This did not show as a problem (since the view is already clicked) but still might be confusing.
I hope this helps! Feel free to post a better solution if you find one.
int tries = 0;
while (tries < MAX_TRIES) {
UiObject2 dateField = mDevice.findObject(
By.descStartsWith(SEARCH_DATE));
if (dateField != null) {
dateField.click();
break;
} else {
tries ++;
new UiScrollable(new UiSelector().
className(android.widget.ViewAnimator.class.getName())).
scrollToEnd(1);
mDevice.wait(Until.findObject(By.descStartsWith(SEARCH_DATE)), 1000);
}
}
I'm using a CalendarView directly, not a DatePicker, b/c its being used as a drop-down/pop-up style dialog where space is factor ( the user clicks a button located to the right of date field, and the CalendarView drops down, appearing directly below the field, aligned/anchored to the button).
There are two show-stopping issues I've ran into, and spent an entire 10 hour day debugging with no resolve.
Issue #1 - Weeks Missing
Weeks appear to missing, and depending on the date, sometimes the week missing will correspond to the "default"/current date (the date the displayed record contains that was loaded from the DB), and so when I display the CalendarView, and call setDate() to auto-select it, no date will appear to be selected (although the CalendarView will be centered around the missing week).
The way I've been fixing this is to manually scroll down, and then back up a few months, and the refresh usually fixes the display. However, I haven't found a way to automate this scrolling, which could be a potential work around. I tried calling setDate() in succession to do this, but it seems to only work on the first call, which brings me to my next issue.
Issue #2 - setDate() Auto-Scrolling Not Working
It seems that only the first call to setDate() will cause the CalendarView to be centered around the corresponding date. If I choose a new date (and store it in private member) and dismiss the popup, and then bring it back up with another dropdown-button click, which will now call setDate() with this new date, then the CalendarView will be centered around the old/previous date, even though the new date is actually hilited (which can be confirmed my manually scrolling down to it).
I can attach code if required, but before spending the time to do so, I just wanted to see if this is a well known issue.
Thank you.
This is just a work-around (also, I've been working in Monodroid/C#, not raw Android)
By first calling SetDate() with a value for the last day of the month previous to the desired display date, and then calling it with the desired date, it seems to work consistently as expected (make sure this date falls between the Max/Min dates of the CalendarView).
However, the second call needs to be posted as a runnable to the UI thread.
Here is some code (note the boolean flags to SetDate, for centering and scroll-animation, neither of which work, hence this work-around):
View view = _inflater.Inflate( Resource.Layout.CalendarViewPicker, null );
CalendarView cvPicker = view.FindViewById<Android.Widget.CalendarView>( Resource.Id.cv_picker );
DateTime lastDay = new DateTime(_date.Year, _date.Month, 1).AddDays(-1);
long lastDayTicks = Ticks( lastDay.Date );
cvPicker.SetDate( lastDayTicks, true, true );
cvPicker.Post( () => {cvPicker.SetDate(Ticks(_date.Date),true,true)} );
Passing _date.Date to Ticks() passes a DateTime with a Time of 12:00am (basically "zeros out" time element), as opposed to passing just _date, which is also a DateTime. Also, a conversion function is needed, since I'm using C#/.NET, to convert the tick-offset
private long Ticks( DateTime date )
{
DateTime _1970 = (new DateTime(1970, 1, 1)).AddDays(-1);
return (date.Ticks - _1970.Ticks) / 10000;
}
Resource.Layout.CalendarViewPicker.axml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="#dimen/calendar_view_width"
android:layout_height="#dimen/calendar_view_height"
android:background="#FF666666">
<Button
android:id="#+id/btn_picker_exit"
android:layout_width="60dp"
android:layout_height="36dp"
android:layout_alignParentTop="true"
android:background="#drawable/cv_button_background"
android:gravity="center"
android:textColor="#FFFFFFFF"
android:textSize="20sp"
android:text="+"/>
<CalendarView
android:id="#+id/cv_picker"
android:layout_width="#dimen/calendar_view_width"
android:layout_height="#dimen/calendar_view_height"
android:shownWeekCount="6"/>
<RelativeLayout/>
My reasoning for the first call to SetDate() with the last day of the previous month, was b/c many times the calendar would popup with the second week of a given month showing as the first visible week, and when scrolling up to see the first week, I would find it missing. So, I figured setting the date with the last day of the previous month might force the first week (ie, the problematic week) to show.
Now with the first week showing, I call SetDate again, but it only works when posted, b/c when posting a runnable to the UI, the event is scheduled to occur after the associated view is rendered. If 2 SetDate()'s are called consecutively (neither in a runnable), say in OnCreate(), then the effects take place before the view is rendered, and so only the second call would take effect, overwriting the first (and therefore loosing the effect of forcing the first week to show).
These two calls also fix the centering/auto-scrolling issue, which is nice too.
I have a custom view with an added method named moveView which changes the location of the view by a X pixels in Y direction when invoked. Now I want to have 6 of these views spread all over the screen and be in constant movement.
What I have:
Handler (I need it for UI updates?)
ApplyMoveRunnable (implements Runnable. Constructor receives custom View, amount of pixels to move, and direction in which it will move)
6 Threads (one for each view)
6 MoveCalcRunnable (one for each thread. implements Runnable)
What I tried:
I tried creating my 6 views and then 6 Threads. Each of these threads' MoveCalcRunnable call the moveView() method within their respective views. This moveView() method calculates the amount of pixels the view will move and the direction in which it will move and sends this information to the ApplyMoveRunnable and then calls the handler.post(applyMoveRunnable).
Simply put, what happens is this:
Instantiate ApplyMoveRunnable applyMoveRunnable
Instantiate Handler handler.
Instantiate 6 MoveCalcRunnable's.
Create 6 threads, one for each view, each holding a MoveCalcRunnable.
Start all threads.
When I run this, at first I see my 6 views, but then one moves and nothing else happens.
Here's the run() method for each of my MoveCalcRunnable:
#Override
public void run() {
Log.e(tag, "Thread locking directionAmountRunnable.");
synchronized (applyMoveRunnable) {
Log.e(tag, "Moving view.");
myView.moveView();
}
}
Can anyone help me get this to work? Or suggest a better, simpler, and/or more efficient way of doing this?
I have a Service that sends an Intent to my Activity every 0.1 seconds. I use it to update a custom implementation of a Chronometer. Here everything goes right. The problem comes when I want to update 14 TextView I have in a TableView inside a Fragment in my Activity. Here the app is very slow.
The method in my Activity where it receives the Intent from the Service:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
long milis = intent.getLongExtra("milis",0);
if( mFragment != null)
mFragment.Update(milis);
}
};
The code inside the Fragment where I update the TextViews:
public void actualizarTiempoJuego(long milis){
// Se recuperan los tiempos acumulados y se aumenta la cantidad pasada como parĂ¡metro
for(int i=0;i<7;++i) {
long mCurrentMilis1 = mVectorMilis1.get(i);
long mCurrentMilis2 = mVectorMilis2.get(i);
TextView1 t1 = mListaTitularLayoutLocal.get(i);
TextView1 t2 = mListaTitularLayoutVisitante.get(i);
t1.setText(String.value(milis + mCurrentMilis1));
t2.setText(String.value(milis + mCurrentMilis2));
}
}
Am I doing anything wrong, or is it just that I'm trying to do something very complex in terms of efficiency?
#Sherif brings up a good point about hidden alpha values that bog down your application a lot.
Depending on your platform you may also want to check
<application android:hardwareAccelerated="true"... />
Another thing you can look into that may help performance is not firing off all those Intents. Once you start firing intents you are getting the system involved and depending on how they are getting resolved it may take some extra time.
For this issue I like to use Handlers. They are more light weight than intent.
You may also want to look at AsyncTask. This is basically like a thread, but also gives hooks that run on the UI Thread so you can perform both perform a background operation and update the UI without have to post runnables.
EDIT: Lastly, you can always run your layouts through the layoutopt tool. I was personally told by Romain Guy himself that if your drawing too slow, than you need to draw less. Just check out a screenshot (from a less than ideal view tree, but well within the max) from the profiling tool. You can see how much of the resources view drawing takes up. It's very important to keep this as lean as possible if you want your app to be responsive.
EDIT: It is no longer called layoutopt, it's called lint. Check your ~/android-sdk/tools/
I have once faced a situation where a fragment was really slow.
I am just predicting that your fragment has some kind of alpha and it is drawn on a 'heavy' activity.
The conclusion is that each time you are setting the text of a textview your whole view hierarchy is being invalidated.
It seems that fragments have this flaw. Anyway, use some layout instead of the fragment and check if it remains 'slow'.
ADDITION: A wrap_content textview will cause much more delay after a setText than a fill_parent textview.
You're likely running into slowdowns due to layout management with TableLayout and TextView. Every time you update text in one of those, a large amount of view measuring has to take place in order to put the characters in the right place on the screen. You should really just profile the app yourself using Traceview to find out. More information at: http://developer.android.com/tools/debugging/debugging-tracing.html
I've had the exact same issue you're seeing with the same type of layout (Fragment > TableLayout > Multiple TextViews). One way to test if your TableLayout/TextView setup is to blame is simply replace all that with a single TextView. That will probably run pretty well. Then put your 14 views into a FrameLayout or RelativeLayout. Even if they all overlap, you should still get decent performance, because it's the complexity of the TableLayout view measurements that's really causing slowdown.
As someone said you can use HardwareAccelerated but this is not a great solution, you will waste ram and cpu if you can't solve it in a different way. A solution probably more safety is to reduce the number of TextView. Try to reduce 14 to 7 and it will go twice faster. Usually is hard to do it but if you put the objects in a strategy position a pair of TextView one above other can be together if you make a TextView with two lines. And don't forget that findViewById is so expensive, if you will use a view object often find it one time and hold its reference.
Benchmarks are always useful for determining where slowness actually comes from, but I feel pretty confident suggesting that sending an Intent is probably much slower than updating 14 TextViews. Sending 10 Intents per second is a sign that you're Doing It Wrong (TM). This is just isn't what they're for.
Am I doing anything wrong, or is it just that I'm trying to do something very complex in terms of efficiency?
Updating 14 TextViews per second isn't inherently complex; you should be able to easily achieve this with a more appropriate application design. ASyncTask or Handler come to mind as possible tools, but it's hard to know what's best without knowing more about exactly what you're trying to do.
You can try to declare vars outside the loop :
public void actualizarTiempoJuego(long milis){
// Se recuperan los tiempos acumulados y se
// aumenta la cantidad pasada como parĂ¡metro
long mCurrentMilis1;
long mCurrentMilis2;
TextView1 t1;
TextView1 t2;
for(int i=0;i<7;++i) {
mCurrentMilis1 = mVectorMilis1.get(i);
mCurrentMilis2 = mVectorMilis2.get(i);
t1 = mListaTitularLayoutLocal.get(i);
t2 = mListaTitularLayoutVisitante.get(i);
t1.setText(String.value(milis + mCurrentMilis1));
t2.setText(String.value(milis + mCurrentMilis2));
}
}
And to setText() with mixed type, you can try setText("" + milis + mCurrentMilis2);