Bug 769145 - Part 5: Search suggestion opt-in animations. r=lucasr

--HG--
rename : mobile/android/base/resources/layout/awesomebar_suggestion_row.xml => mobile/android/base/resources/layout/awesomebar_suggestion_row.xml.in
This commit is contained in:
Brian Nicholson 2012-10-05 17:27:12 -07:00
parent d6318a0457
commit 34d3b2814d
6 changed files with 175 additions and 15 deletions

View File

@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.widget.RelativeLayout;
public class AnimatedHeightLayout extends RelativeLayout {
private static final String LOGTAG = "GeckoAnimatedHeightLayout";
private static final int ANIMATION_DURATION = 100;
private boolean mAnimating = false;
public AnimatedHeightLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int oldHeight = getMeasuredHeight();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = getMeasuredHeight();
if (!mAnimating && oldHeight != 0 && oldHeight != newHeight) {
mAnimating = true;
setMeasuredDimension(getMeasuredWidth(), oldHeight);
// Animate the difference of suggestion row height
Animation anim = new HeightChangeAnimation(this, oldHeight, newHeight);
anim.setDuration(ANIMATION_DURATION);
anim.setInterpolator(new DecelerateInterpolator());
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationRepeat(Animation animation) {}
public void onAnimationEnd(Animation animation) {
post(new Runnable() {
public void run() {
getLayoutParams().height = LayoutParams.WRAP_CONTENT;
mAnimating = false;
}
});
}
});
startAnimation(anim);
}
}
}

View File

@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class HeightChangeAnimation extends Animation {
int mFromHeight;
int mToHeight;
View mView;
public HeightChangeAnimation(View view, int fromHeight, int toHeight) {
mView = view;
mFromHeight = fromHeight;
mToHeight = toHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mView.getLayoutParams().height = Math.round((mFromHeight * (1 - interpolatedTime)) + (mToHeight * interpolatedTime));
mView.requestLayout();
}
}

View File

@ -41,6 +41,7 @@ FENNEC_JAVA_FILES = \
AndroidImport.java \
AndroidImportPreference.java \
AlertNotification.java \
AnimatedHeightLayout.java \
AwesomeBar.java \
AwesomebarResultHandler.java \
AwesomeBarTabs.java \
@ -86,6 +87,7 @@ FENNEC_JAVA_FILES = \
GeckoThread.java \
GlobalHistory.java \
GeckoViewsFactory.java \
HeightChangeAnimation.java \
InputMethods.java \
LinkPreference.java \
LinkTextView.java \
@ -201,6 +203,7 @@ FENNEC_PP_JAVA_FILES = \
FENNEC_PP_XML_FILES = \
res/layout/abouthome_content.xml \
res/layout/awesomebar_suggestion_row.xml \
res/layout/browser_toolbar.xml \
res/layout/browser_toolbar_menu.xml \
res/layout-land-v14/browser_toolbar.xml \
@ -331,7 +334,6 @@ RES_LAYOUT = \
res/layout/awesomebar_row.xml \
res/layout/awesomebar_suggestion_item.xml \
res/layout/awesomebar_suggestion_prompt.xml \
res/layout/awesomebar_suggestion_row.xml \
res/layout/awesomebar_search.xml \
res/layout/awesomebar_tab_indicator.xml \
res/layout/awesomebar_tabs.xml \

View File

@ -39,6 +39,10 @@ public class SuggestClient {
// used by robocop for testing; referenced via reflection
private boolean mCheckNetwork;
// used to make suggestions appear instantly after opt-in
private String mPrevQuery;
private ArrayList<String> mPrevResults;
public SuggestClient(Context context, String suggestTemplate, int timeout, int maxResults) {
mContext = context;
mMaxResults = maxResults;
@ -55,6 +59,9 @@ public class SuggestClient {
* Queries for a given search term and returns an ArrayList of suggestions.
*/
public ArrayList<String> query(String query) {
if (query.equals(mPrevQuery))
return mPrevResults;
ArrayList<String> suggestions = new ArrayList<String>();
if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) {
return suggestions;
@ -113,6 +120,9 @@ public class SuggestClient {
} catch (Exception e) {
Log.e(LOGTAG, "Error", e);
}
mPrevQuery = query;
mPrevResults = suggestions;
return suggestions;
}

View File

@ -23,6 +23,10 @@ import android.os.AsyncTask;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
@ -31,8 +35,10 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.FilterQueryProvider;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@ -50,6 +56,7 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
private static final int SUGGESTION_TIMEOUT = 3000;
private static final int SUGGESTION_MAX = 3;
private static final int ANIMATION_DURATION = 250;
private String mSearchTerm;
private ArrayList<SearchEngine> mSearchEngines;
@ -59,6 +66,7 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
private AwesomeBarCursorAdapter mCursorAdapter = null;
private boolean mTelemetrySent = false;
private LinearLayout mAllPagesView;
private boolean mAnimateSuggestions;
private View mSuggestionsOptInPrompt;
private class SearchEntryViewHolder {
@ -145,6 +153,17 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
}
}
/**
* Query for suggestions, but don't show them yet.
*/
private void primeSuggestions() {
GeckoAppShell.getHandler().post(new Runnable() {
public void run() {
mSuggestClient.query(mSearchTerm);
}
});
}
private void filterSuggestions(String searchTerm) {
// cancel previous query
if (mSuggestTask != null) {
@ -341,7 +360,7 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
SearchEntryViewHolder viewHolder = null;
if (convertView == null) {
convertView = getInflater().inflate(R.layout.awesomebar_suggestion_row, null);
convertView = getInflater().inflate(R.layout.awesomebar_suggestion_row, getListView(), false);
viewHolder = new SearchEntryViewHolder();
viewHolder.suggestionView = (FlowLayout) convertView.findViewById(R.id.suggestion_layout);
@ -422,6 +441,8 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
// add additional suggestions given by this engine
int recycledSuggestionCount = suggestionView.getChildCount();
int suggestionCount = engine.suggestions.size();
boolean showedSuggestions = false;
for (int i = 0; i < suggestionCount; i++) {
String suggestion = engine.suggestions.get(i);
View suggestionItem = null;
@ -439,12 +460,23 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
suggestionItem.setOnClickListener(clickListener);
suggestionItem.setOnLongClickListener(longClickListener);
if (mAnimateSuggestions) {
showedSuggestions = true;
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(ANIMATION_DURATION);
anim.setStartOffset(i * ANIMATION_DURATION);
suggestionItem.startAnimation(anim);
}
}
// hide extra suggestions that have been recycled
for (int i = suggestionCount + 1; i < recycledSuggestionCount; i++) {
suggestionView.getChildAt(i).setVisibility(View.GONE);
}
if (showedSuggestions)
mAnimateSuggestions = false;
}
};
@ -546,21 +578,57 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
}
private void setSuggestionsEnabled(final boolean enabled) {
// Make suggestions appear immediately after the user opts in
primeSuggestions();
// Pref observer in gecko will also set prompted = true
PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
getAllPagesView().post(new Runnable() {
public void run() {
getAllPagesView().removeView(mSuggestionsOptInPrompt);
mSuggestionsOptInPrompt = null;
TranslateAnimation anim1 = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0);
anim1.setDuration(ANIMATION_DURATION);
anim1.setInterpolator(new AccelerateInterpolator());
anim1.setFillAfter(true);
mSuggestionsOptInPrompt.setAnimation(anim1);
if (enabled) {
mSuggestionsEnabled = enabled;
getCursorAdapter().notifyDataSetChanged();
filterSuggestions(mSearchTerm);
}
TranslateAnimation anim2 = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight());
anim2.setDuration(ANIMATION_DURATION);
anim2.setFillAfter(true);
anim2.setStartOffset(anim1.getDuration());
anim2.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) {
// Increase the height of the view so a gap isn't shown during animation
getAllPagesView().getLayoutParams().height = getAllPagesView().getHeight() +
mSuggestionsOptInPrompt.getHeight();
getAllPagesView().requestLayout();
}
public void onAnimationRepeat(Animation a) {}
public void onAnimationEnd(Animation a) {
// Removing the view immediately results in a NPE in
// dispatchDraw(), possibly because this callback executes
// before drawing is finished. Posting this as a Runnable fixes
// the issue.
getAllPagesView().post(new Runnable() {
public void run() {
getAllPagesView().removeView(mSuggestionsOptInPrompt);
getListView().clearAnimation();
mSuggestionsOptInPrompt = null;
if (enabled) {
// Reset the view height
getAllPagesView().getLayoutParams().height = LayoutParams.FILL_PARENT;
mSuggestionsEnabled = enabled;
mAnimateSuggestions = true;
getCursorAdapter().notifyDataSetChanged();
filterSuggestions(mSearchTerm);
}
}
});
}
});
mSuggestionsOptInPrompt.startAnimation(anim1);
getListView().startAnimation(anim2);
}
public void handleMessage(String event, final JSONObject message) {

View File

@ -1,14 +1,15 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.mozilla.gecko.AnimatedHeightLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res/@ANDROID_PACKAGE_NAME@"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/awesomebar_row_height"
android:gravity="center_vertical"
android:padding="6dip">
android:padding="7dip">
<ImageView android:id="@+id/suggestion_icon"
android:layout_width="32dip"
@ -29,4 +30,4 @@
</org.mozilla.gecko.FlowLayout>
</RelativeLayout>
</org.mozilla.gecko.AnimatedHeightLayout>