diff --git a/samples/browseable/AdapterTransition/AndroidManifest.xml b/samples/browseable/AdapterTransition/AndroidManifest.xml
new file mode 100644
index 000000000..01b414d9c
--- /dev/null
+++ b/samples/browseable/AdapterTransition/AndroidManifest.xml
@@ -0,0 +1,42 @@
+
+
+
+ + Transition cannot be directly applied to AdapterViews. In this sample, we demonstrate how to create a overlay layout and run a Transition on it. + +
diff --git a/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_grid.png b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_grid.png new file mode 100644 index 000000000..e04f4a7f5 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_grid.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_list.png b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_list.png new file mode 100644 index 000000000..4131dba4e Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_action_list.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_launcher.png b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..b7a67c0cf Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-hdpi/tile.9.png b/samples/browseable/AdapterTransition/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_grid.png b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_grid.png new file mode 100644 index 000000000..f2a83e38c Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_grid.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_list.png b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_list.png new file mode 100644 index 000000000..e248a488e Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_action_list.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_launcher.png b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..1c9fc097d Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p1.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p1.jpg new file mode 100644 index 000000000..10f07acf1 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p1.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p10.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p10.jpg new file mode 100644 index 000000000..4272f4cc0 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p10.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p11.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p11.jpg new file mode 100644 index 000000000..c5722b229 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p11.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p2.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p2.jpg new file mode 100644 index 000000000..ca380ae4e Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p2.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p3.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p3.jpg new file mode 100644 index 000000000..6fc71e765 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p3.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p4.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p4.jpg new file mode 100644 index 000000000..153c1ffe8 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p4.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p5.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p5.jpg new file mode 100644 index 000000000..46d6a13df Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p5.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p6.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p6.jpg new file mode 100644 index 000000000..89ccb8329 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p6.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p7.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p7.jpg new file mode 100644 index 000000000..7e9546dde Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p7.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p8.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p8.jpg new file mode 100644 index 000000000..21e25ba17 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p8.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-nodpi/p9.jpg b/samples/browseable/AdapterTransition/res/drawable-nodpi/p9.jpg new file mode 100644 index 000000000..79854cb64 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-nodpi/p9.jpg differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_grid.png b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_grid.png new file mode 100644 index 000000000..ecd39b530 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_grid.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_list.png b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_list.png new file mode 100644 index 000000000..e7e510d07 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_action_list.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..11b992810 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_grid.png b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_grid.png new file mode 100644 index 000000000..3ba98fc15 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_grid.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_list.png b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_list.png new file mode 100644 index 000000000..d18773288 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_action_list.png differ diff --git a/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..f136c9f14 Binary files /dev/null and b/samples/browseable/AdapterTransition/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/AdapterTransition/res/layout-w720dp/activity_main.xml b/samples/browseable/AdapterTransition/res/layout-w720dp/activity_main.xml new file mode 100755 index 000000000..c9a52f621 --- /dev/null +++ b/samples/browseable/AdapterTransition/res/layout-w720dp/activity_main.xml @@ -0,0 +1,73 @@ + ++ * For devices with displays with a width of 720dp or greater, the sample log is always visible, + * on other devices it's visibility is controlled by an item on the Action Bar. + */ +public class MainActivity extends SampleActivityBase { + + public static final String TAG = "MainActivity"; + + // Whether the Log Fragment is currently shown + private boolean mLogShown; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + AdapterTransitionFragment fragment = new AdapterTransitionFragment(); + transaction.replace(R.id.sample_content_fragment, fragment); + transaction.commit(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); + logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator); + logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log); + + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_toggle_log: + mLogShown = !mLogShown; + ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output); + if (mLogShown) { + output.setDisplayedChild(1); + } else { + output.setDisplayedChild(0); + } + supportInvalidateOptionsMenu(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** Create a chain of targets that will receive log data */ + @Override + public void initializeLogging() { + // Wraps Android's native log framework. + LogWrapper logWrapper = new LogWrapper(); + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + Log.setLogNode(logWrapper); + + // Filter strips out everything except the message text. + MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); + logWrapper.setNext(msgFilter); + + // On screen logging via a fragment with a TextView. + LogFragment logFragment = (LogFragment) getSupportFragmentManager() + .findFragmentById(R.id.log_fragment); + msgFilter.setNext(logFragment.getLogView()); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/Meat.java b/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/Meat.java new file mode 100644 index 000000000..bca1c5f00 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/Meat.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.adaptertransition; + +/** + * Sample data. + */ +public class Meat { + + public int resourceId; + public String title; + + public Meat(int resourceId, String title) { + this.resourceId = resourceId; + this.title = title; + } + + public static final Meat[] MEATS = { + new Meat(R.drawable.p1, "First"), + new Meat(R.drawable.p2, "Second"), + new Meat(R.drawable.p3, "Third"), + new Meat(R.drawable.p4, "Fourth"), + new Meat(R.drawable.p5, "Fifth"), + new Meat(R.drawable.p6, "Sixth"), + new Meat(R.drawable.p7, "Seventh"), + new Meat(R.drawable.p8, "Eighth"), + new Meat(R.drawable.p9, "Ninth"), + new Meat(R.drawable.p10, "Tenth"), + new Meat(R.drawable.p11, "Eleventh"), + }; + +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/MeatAdapter.java b/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/MeatAdapter.java new file mode 100644 index 000000000..c7630cedf --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.adaptertransition/MeatAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.adaptertransition; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * This class provides data as Views. It is designed to support both ListView and GridView by + * changing a layout resource file to inflate. + */ +public class MeatAdapter extends BaseAdapter { + + private final LayoutInflater mLayoutInflater; + private final int mResourceId; + + /** + * Create a new instance of {@link MeatAdapter}. + * + * @param inflater The layout inflater. + * @param resourceId The resource ID for the layout to be used. The layout should contain an + * ImageView with ID of "meat_image" and a TextView with ID of "meat_title". + */ + public MeatAdapter(LayoutInflater inflater, int resourceId) { + mLayoutInflater = inflater; + mResourceId = resourceId; + } + + @Override + public int getCount() { + return Meat.MEATS.length; + } + + @Override + public Meat getItem(int position) { + return Meat.MEATS[position]; + } + + @Override + public long getItemId(int position) { + return Meat.MEATS[position].resourceId; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View view; + final ViewHolder holder; + if (null == convertView) { + view = mLayoutInflater.inflate(mResourceId, parent, false); + holder = new ViewHolder(); + assert view != null; + holder.image = (ImageView) view.findViewById(R.id.meat_image); + holder.title = (TextView) view.findViewById(R.id.meat_title); + view.setTag(holder); + } else { + view = convertView; + holder = (ViewHolder) view.getTag(); + } + Meat meat = getItem(position); + holder.image.setImageResource(meat.resourceId); + holder.title.setText(meat.title); + return view; + } + + private static class ViewHolder { + public ImageView image; + public TextView title; + } + +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/activities/SampleActivityBase.java b/samples/browseable/AdapterTransition/src/com.example.android.common/activities/SampleActivityBase.java new file mode 100644 index 000000000..3228927b7 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/activities/SampleActivityBase.java @@ -0,0 +1,52 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.example.android.common.activities; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogWrapper; + +/** + * Base launcher activity, to handle most of the common plumbing for samples. + */ +public class SampleActivityBase extends FragmentActivity { + + public static final String TAG = "SampleActivityBase"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + super.onStart(); + initializeLogging(); + } + + /** Set up targets to receive log data */ + public void initializeLogging() { + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + // Wraps Android's native log framework + LogWrapper logWrapper = new LogWrapper(); + Log.setLogNode(logWrapper); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/Log.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/Log.java new file mode 100644 index 000000000..17503c568 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/Log.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + *
When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.
+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogFragment.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogFragment.java new file mode 100644 index 000000000..b302acd4b --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogNode.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogNode.java new file mode 100644 index 000000000..bc37cabc0 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogView.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogView.java new file mode 100644 index 000000000..c01542b91 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogWrapper.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogWrapper.java new file mode 100644 index 000000000..16a9e7ba2 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java new file mode 100644 index 000000000..19967dcd4 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabLayout.java b/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabLayout.java new file mode 100644 index 000000000..20049e335 --- /dev/null +++ b/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabLayout.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.view; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.TextView; + +/** + * To be used with ViewPager to provide a tab indicator component which give constant feedback as to + * the user's scroll progress. + *+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. + *
+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors + * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The + * alternative is via the {@link TabColorizer} interface which provides you complete control over + * which color is used for any individual position. + *
+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
+ * providing the layout ID of your custom layout.
+ */
+public class SlidingTabLayout extends HorizontalScrollView {
+
+ /**
+ * Allows complete control over the colors drawn in the tab layout. Set with
+ * {@link #setCustomTabColorizer(TabColorizer)}.
+ */
+ public interface TabColorizer {
+
+ /**
+ * @return return the color of the indicator used when {@code position} is selected.
+ */
+ int getIndicatorColor(int position);
+
+ /**
+ * @return return the color of the divider drawn to the right of {@code position}.
+ */
+ int getDividerColor(int position);
+
+ }
+
+ private static final int TITLE_OFFSET_DIPS = 24;
+ private static final int TAB_VIEW_PADDING_DIPS = 16;
+ private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
+
+ private int mTitleOffset;
+
+ private int mTabViewLayoutId;
+ private int mTabViewTextViewId;
+
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
+
+ private final SlidingTabStrip mTabStrip;
+
+ public SlidingTabLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // Disable the Scroll Bar
+ setHorizontalScrollBarEnabled(false);
+ // Make sure that the Tab Strips fills this View
+ setFillViewport(true);
+
+ mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+
+ mTabStrip = new SlidingTabStrip(context);
+ addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Set the custom {@link TabColorizer} to be used.
+ *
+ * If you only require simple custmisation then you can use
+ * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
+ * similar effects.
+ */
+ public void setCustomTabColorizer(TabColorizer tabColorizer) {
+ mTabStrip.setCustomTabColorizer(tabColorizer);
+ }
+
+ /**
+ * Sets the colors to be used for indicating the selected tab. These colors are treated as a
+ * circular array. Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setSelectedIndicatorColors(int... colors) {
+ mTabStrip.setSelectedIndicatorColors(colors);
+ }
+
+ /**
+ * Sets the colors to be used for tab dividers. These colors are treated as a circular array.
+ * Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setDividerColors(int... colors) {
+ mTabStrip.setDividerColors(colors);
+ }
+
+ /**
+ * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
+ * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
+ * that the layout can update it's scroll position correctly.
+ *
+ * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
+ */
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mViewPagerPageChangeListener = listener;
+ }
+
+ /**
+ * Set the custom layout to be inflated for the tab views.
+ *
+ * @param layoutResId Layout id to be inflated
+ * @param textViewId id of the {@link TextView} in the inflated view
+ */
+ public void setCustomTabView(int layoutResId, int textViewId) {
+ mTabViewLayoutId = layoutResId;
+ mTabViewTextViewId = textViewId;
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the pager content
+ * (number of tabs and tab titles) does not change after this call has been made.
+ */
+ public void setViewPager(ViewPager viewPager) {
+ mTabStrip.removeAllViews();
+
+ mViewPager = viewPager;
+ if (viewPager != null) {
+ viewPager.setOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ /**
+ * Create a default view to be used for tabs. This is called if a custom tab view is not set via
+ * {@link #setCustomTabView(int, int)}.
+ */
+ protected TextView createDefaultTabView(Context context) {
+ TextView textView = new TextView(context);
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // If we're running on Honeycomb or newer, then we can use the Theme's
+ // selectableItemBackground to ensure that the View has a pressed state
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true);
+ textView.setBackgroundResource(outValue.resourceId);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
+ textView.setAllCaps(true);
+ }
+
+ int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
+ textView.setPadding(padding, padding, padding, padding);
+
+ return textView;
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+ final View.OnClickListener tabClickListener = new TabClickListener();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ View tabView = null;
+ TextView tabTitleView = null;
+
+ if (mTabViewLayoutId != 0) {
+ // If there is a custom tab view layout id set, try and inflate it
+ tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
+ false);
+ tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
+ }
+
+ if (tabView == null) {
+ tabView = createDefaultTabView(getContext());
+ }
+
+ if (tabTitleView == null && TextView.class.isInstance(tabView)) {
+ tabTitleView = (TextView) tabView;
+ }
+
+ tabTitleView.setText(adapter.getPageTitle(i));
+ tabView.setOnClickListener(tabClickListener);
+
+ mTabStrip.addView(tabView);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager != null) {
+ scrollToTab(mViewPager.getCurrentItem(), 0);
+ }
+ }
+
+ private void scrollToTab(int tabIndex, int positionOffset) {
+ final int tabStripChildCount = mTabStrip.getChildCount();
+ if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
+ return;
+ }
+
+ View selectedChild = mTabStrip.getChildAt(tabIndex);
+ if (selectedChild != null) {
+ int targetScrollX = selectedChild.getLeft() + positionOffset;
+
+ if (tabIndex > 0 || positionOffset > 0) {
+ // If we're not at the first child and are mid-scroll, make sure we obey the offset
+ targetScrollX -= mTitleOffset;
+ }
+
+ scrollTo(targetScrollX, 0);
+ }
+ }
+
+ private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onViewPagerPageChanged(position, positionOffset);
+
+ View selectedTitle = mTabStrip.getChildAt(position);
+ int extraOffset = (selectedTitle != null)
+ ? (int) (positionOffset * selectedTitle.getWidth())
+ : 0;
+ scrollToTab(position, extraOffset);
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
+ positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mTabStrip.onViewPagerPageChanged(position, 0f);
+ scrollToTab(position, 0);
+ }
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageSelected(position);
+ }
+ }
+
+ }
+
+ private class TabClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ if (v == mTabStrip.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabStrip.java b/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabStrip.java
new file mode 100644
index 000000000..d5bbbae59
--- /dev/null
+++ b/samples/browseable/AdapterTransition/src/com.example.android.common/view/SlidingTabStrip.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.common.view;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class SlidingTabStrip extends LinearLayout {
+
+ private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
+ private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
+ private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
+ private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
+
+ private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
+ private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
+ private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
+
+ private final int mBottomBorderThickness;
+ private final Paint mBottomBorderPaint;
+
+ private final int mSelectedIndicatorThickness;
+ private final Paint mSelectedIndicatorPaint;
+
+ private final int mDefaultBottomBorderColor;
+
+ private final Paint mDividerPaint;
+ private final float mDividerHeight;
+
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ private SlidingTabLayout.TabColorizer mCustomTabColorizer;
+ private final SimpleTabColorizer mDefaultTabColorizer;
+
+ SlidingTabStrip(Context context) {
+ this(context, null);
+ }
+
+ SlidingTabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+
+ final float density = getResources().getDisplayMetrics().density;
+
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
+ final int themeForegroundColor = outValue.data;
+
+ mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
+ DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
+
+ mDefaultTabColorizer = new SimpleTabColorizer();
+ mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
+ mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
+ DEFAULT_DIVIDER_COLOR_ALPHA));
+
+ mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
+ mBottomBorderPaint = new Paint();
+ mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
+
+ mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
+ mSelectedIndicatorPaint = new Paint();
+
+ mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
+ mDividerPaint = new Paint();
+ mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
+ }
+
+ void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
+ mCustomTabColorizer = customTabColorizer;
+ invalidate();
+ }
+
+ void setSelectedIndicatorColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setIndicatorColors(colors);
+ invalidate();
+ }
+
+ void setDividerColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setDividerColors(colors);
+ invalidate();
+ }
+
+ void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
+ final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
+ ? mCustomTabColorizer
+ : mDefaultTabColorizer;
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ int right = selectedTitle.getRight();
+ int color = tabColorizer.getIndicatorColor(mSelectedPosition);
+
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
+ if (color != nextColor) {
+ color = blendColors(nextColor, color, mSelectionOffset);
+ }
+
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+
+ mSelectedIndicatorPaint.setColor(color);
+
+ canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
+ height, mSelectedIndicatorPaint);
+ }
+
+ // Thin underline along the entire bottom edge
+ canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
+
+ // Vertical separators between the titles
+ int separatorTop = (height - dividerHeightPx) / 2;
+ for (int i = 0; i < childCount - 1; i++) {
+ View child = getChildAt(i);
+ mDividerPaint.setColor(tabColorizer.getDividerColor(i));
+ canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
+ separatorTop + dividerHeightPx, mDividerPaint);
+ }
+ }
+
+ /**
+ * Set the alpha value of the {@code color} to be the given {@code alpha} value.
+ */
+ private static int setColorAlpha(int color, byte alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Blend {@code color1} and {@code color2} using the given ratio.
+ *
+ * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
+ * 0.0 will return {@code color2}.
+ */
+ private static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRation = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
+ private int[] mIndicatorColors;
+ private int[] mDividerColors;
+
+ @Override
+ public final int getIndicatorColor(int position) {
+ return mIndicatorColors[position % mIndicatorColors.length];
+ }
+
+ @Override
+ public final int getDividerColor(int position) {
+ return mDividerColors[position % mDividerColors.length];
+ }
+
+ void setIndicatorColors(int... colors) {
+ mIndicatorColors = colors;
+ }
+
+ void setDividerColors(int... colors) {
+ mDividerColors = colors;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/AdvancedImmersiveMode/res/drawable-hdpi/ic_launcher.png b/samples/browseable/AdvancedImmersiveMode/res/drawable-hdpi/ic_launcher.png
index b1efaf4b2..b96e6a546 100644
Binary files a/samples/browseable/AdvancedImmersiveMode/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/AdvancedImmersiveMode/res/drawable-hdpi/ic_launcher.png differ
diff --git a/samples/browseable/AdvancedImmersiveMode/res/drawable-mdpi/ic_launcher.png b/samples/browseable/AdvancedImmersiveMode/res/drawable-mdpi/ic_launcher.png
index f5f9244f2..1294d5b7b 100644
Binary files a/samples/browseable/AdvancedImmersiveMode/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/AdvancedImmersiveMode/res/drawable-mdpi/ic_launcher.png differ
diff --git a/samples/browseable/AdvancedImmersiveMode/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/AdvancedImmersiveMode/res/drawable-xhdpi/ic_launcher.png
index 5d07b3f06..9f101ffaf 100644
Binary files a/samples/browseable/AdvancedImmersiveMode/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/AdvancedImmersiveMode/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/samples/browseable/AdvancedImmersiveMode/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/AdvancedImmersiveMode/res/drawable-xxhdpi/ic_launcher.png
index 6ef21e1f4..7a195a128 100644
Binary files a/samples/browseable/AdvancedImmersiveMode/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/AdvancedImmersiveMode/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/samples/browseable/AdvancedImmersiveMode/res/layout/activity_main.xml b/samples/browseable/AdvancedImmersiveMode/res/layout/activity_main.xml
index bc5a57591..1ae4f981e 100755
--- a/samples/browseable/AdvancedImmersiveMode/res/layout/activity_main.xml
+++ b/samples/browseable/AdvancedImmersiveMode/res/layout/activity_main.xml
@@ -14,25 +14,52 @@
limitations under the License.
-->
+ * For devices with displays with a width of 720dp or greater, the sample log is always visible,
+ * on other devices it's visibility is controlled by an item on the Action Bar.
*/
public class MainActivity extends SampleActivityBase {
public static final String TAG = "MainActivity";
- public static final String FRAGTAG = "AdvancedImmersiveModeFragment";
+ // Whether the Log Fragment is currently shown
+ private boolean mLogShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- if (getSupportFragmentManager().findFragmentByTag(FRAGTAG) == null ) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- AdvancedImmersiveModeFragment fragment = new AdvancedImmersiveModeFragment();
- transaction.add(fragment, FRAGTAG);
- transaction.commit();
- }
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ AdvancedImmersiveModeFragment fragment = new AdvancedImmersiveModeFragment();
+ transaction.replace(R.id.sample_content_fragment, fragment);
+ transaction.commit();
}
@Override
@@ -58,6 +62,32 @@ public class MainActivity extends SampleActivityBase {
return true;
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
+ logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
+ logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_toggle_log:
+ mLogShown = !mLogShown;
+ ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
+ if (mLogShown) {
+ output.setDisplayedChild(1);
+ } else {
+ output.setDisplayedChild(0);
+ }
+ supportInvalidateOptionsMenu();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
/** Create a chain of targets that will receive log data */
@Override
public void initializeLogging() {
diff --git a/samples/browseable/BasicTransition/AndroidManifest.xml b/samples/browseable/BasicTransition/AndroidManifest.xml
new file mode 100644
index 000000000..b4698d688
--- /dev/null
+++ b/samples/browseable/BasicTransition/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+ + This sample demonstrates the basic use of the transition framework introduced in KitKat. + Select each of the RadioButtons to switch between the Scenes. + +
diff --git a/samples/browseable/BasicTransition/res/drawable-hdpi/ic_launcher.png b/samples/browseable/BasicTransition/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..0f5d36076 Binary files /dev/null and b/samples/browseable/BasicTransition/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicTransition/res/drawable-hdpi/tile.9.png b/samples/browseable/BasicTransition/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/BasicTransition/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/BasicTransition/res/drawable-mdpi/ic_launcher.png b/samples/browseable/BasicTransition/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..72d85c944 Binary files /dev/null and b/samples/browseable/BasicTransition/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicTransition/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/BasicTransition/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cf93e6994 Binary files /dev/null and b/samples/browseable/BasicTransition/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicTransition/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/BasicTransition/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..149a98491 Binary files /dev/null and b/samples/browseable/BasicTransition/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicTransition/res/drawable/oval.xml b/samples/browseable/BasicTransition/res/drawable/oval.xml new file mode 100644 index 000000000..07f3abd9e --- /dev/null +++ b/samples/browseable/BasicTransition/res/drawable/oval.xml @@ -0,0 +1,20 @@ + + ++ * For devices with displays with a width of 720dp or greater, the sample log is always visible, + * on other devices it's visibility is controlled by an item on the Action Bar. + */ +public class MainActivity extends SampleActivityBase { + + public static final String TAG = "MainActivity"; + + // Whether the Log Fragment is currently shown + private boolean mLogShown; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + BasicTransitionFragment fragment = new BasicTransitionFragment(); + transaction.replace(R.id.sample_content_fragment, fragment); + transaction.commit(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); + logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator); + logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log); + + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_toggle_log: + mLogShown = !mLogShown; + ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output); + if (mLogShown) { + output.setDisplayedChild(1); + } else { + output.setDisplayedChild(0); + } + supportInvalidateOptionsMenu(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** Create a chain of targets that will receive log data */ + @Override + public void initializeLogging() { + // Wraps Android's native log framework. + LogWrapper logWrapper = new LogWrapper(); + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + Log.setLogNode(logWrapper); + + // Filter strips out everything except the message text. + MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); + logWrapper.setNext(msgFilter); + + // On screen logging via a fragment with a TextView. + LogFragment logFragment = (LogFragment) getSupportFragmentManager() + .findFragmentById(R.id.log_fragment); + msgFilter.setNext(logFragment.getLogView()); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/activities/SampleActivityBase.java b/samples/browseable/BasicTransition/src/com.example.android.common/activities/SampleActivityBase.java new file mode 100644 index 000000000..3228927b7 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/activities/SampleActivityBase.java @@ -0,0 +1,52 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.example.android.common.activities; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogWrapper; + +/** + * Base launcher activity, to handle most of the common plumbing for samples. + */ +public class SampleActivityBase extends FragmentActivity { + + public static final String TAG = "SampleActivityBase"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + super.onStart(); + initializeLogging(); + } + + /** Set up targets to receive log data */ + public void initializeLogging() { + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + // Wraps Android's native log framework + LogWrapper logWrapper = new LogWrapper(); + Log.setLogNode(logWrapper); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/Log.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/Log.java new file mode 100644 index 000000000..17503c568 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/Log.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + *
When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.
+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogFragment.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogFragment.java new file mode 100644 index 000000000..b302acd4b --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogNode.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogNode.java new file mode 100644 index 000000000..bc37cabc0 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogView.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogView.java new file mode 100644 index 000000000..c01542b91 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogWrapper.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogWrapper.java new file mode 100644 index 000000000..16a9e7ba2 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java b/samples/browseable/BasicTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java new file mode 100644 index 000000000..19967dcd4 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabLayout.java b/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabLayout.java new file mode 100644 index 000000000..20049e335 --- /dev/null +++ b/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabLayout.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.view; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.TextView; + +/** + * To be used with ViewPager to provide a tab indicator component which give constant feedback as to + * the user's scroll progress. + *+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. + *
+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors + * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The + * alternative is via the {@link TabColorizer} interface which provides you complete control over + * which color is used for any individual position. + *
+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
+ * providing the layout ID of your custom layout.
+ */
+public class SlidingTabLayout extends HorizontalScrollView {
+
+ /**
+ * Allows complete control over the colors drawn in the tab layout. Set with
+ * {@link #setCustomTabColorizer(TabColorizer)}.
+ */
+ public interface TabColorizer {
+
+ /**
+ * @return return the color of the indicator used when {@code position} is selected.
+ */
+ int getIndicatorColor(int position);
+
+ /**
+ * @return return the color of the divider drawn to the right of {@code position}.
+ */
+ int getDividerColor(int position);
+
+ }
+
+ private static final int TITLE_OFFSET_DIPS = 24;
+ private static final int TAB_VIEW_PADDING_DIPS = 16;
+ private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
+
+ private int mTitleOffset;
+
+ private int mTabViewLayoutId;
+ private int mTabViewTextViewId;
+
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
+
+ private final SlidingTabStrip mTabStrip;
+
+ public SlidingTabLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // Disable the Scroll Bar
+ setHorizontalScrollBarEnabled(false);
+ // Make sure that the Tab Strips fills this View
+ setFillViewport(true);
+
+ mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+
+ mTabStrip = new SlidingTabStrip(context);
+ addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Set the custom {@link TabColorizer} to be used.
+ *
+ * If you only require simple custmisation then you can use
+ * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
+ * similar effects.
+ */
+ public void setCustomTabColorizer(TabColorizer tabColorizer) {
+ mTabStrip.setCustomTabColorizer(tabColorizer);
+ }
+
+ /**
+ * Sets the colors to be used for indicating the selected tab. These colors are treated as a
+ * circular array. Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setSelectedIndicatorColors(int... colors) {
+ mTabStrip.setSelectedIndicatorColors(colors);
+ }
+
+ /**
+ * Sets the colors to be used for tab dividers. These colors are treated as a circular array.
+ * Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setDividerColors(int... colors) {
+ mTabStrip.setDividerColors(colors);
+ }
+
+ /**
+ * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
+ * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
+ * that the layout can update it's scroll position correctly.
+ *
+ * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
+ */
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mViewPagerPageChangeListener = listener;
+ }
+
+ /**
+ * Set the custom layout to be inflated for the tab views.
+ *
+ * @param layoutResId Layout id to be inflated
+ * @param textViewId id of the {@link TextView} in the inflated view
+ */
+ public void setCustomTabView(int layoutResId, int textViewId) {
+ mTabViewLayoutId = layoutResId;
+ mTabViewTextViewId = textViewId;
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the pager content
+ * (number of tabs and tab titles) does not change after this call has been made.
+ */
+ public void setViewPager(ViewPager viewPager) {
+ mTabStrip.removeAllViews();
+
+ mViewPager = viewPager;
+ if (viewPager != null) {
+ viewPager.setOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ /**
+ * Create a default view to be used for tabs. This is called if a custom tab view is not set via
+ * {@link #setCustomTabView(int, int)}.
+ */
+ protected TextView createDefaultTabView(Context context) {
+ TextView textView = new TextView(context);
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // If we're running on Honeycomb or newer, then we can use the Theme's
+ // selectableItemBackground to ensure that the View has a pressed state
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true);
+ textView.setBackgroundResource(outValue.resourceId);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
+ textView.setAllCaps(true);
+ }
+
+ int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
+ textView.setPadding(padding, padding, padding, padding);
+
+ return textView;
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+ final View.OnClickListener tabClickListener = new TabClickListener();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ View tabView = null;
+ TextView tabTitleView = null;
+
+ if (mTabViewLayoutId != 0) {
+ // If there is a custom tab view layout id set, try and inflate it
+ tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
+ false);
+ tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
+ }
+
+ if (tabView == null) {
+ tabView = createDefaultTabView(getContext());
+ }
+
+ if (tabTitleView == null && TextView.class.isInstance(tabView)) {
+ tabTitleView = (TextView) tabView;
+ }
+
+ tabTitleView.setText(adapter.getPageTitle(i));
+ tabView.setOnClickListener(tabClickListener);
+
+ mTabStrip.addView(tabView);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager != null) {
+ scrollToTab(mViewPager.getCurrentItem(), 0);
+ }
+ }
+
+ private void scrollToTab(int tabIndex, int positionOffset) {
+ final int tabStripChildCount = mTabStrip.getChildCount();
+ if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
+ return;
+ }
+
+ View selectedChild = mTabStrip.getChildAt(tabIndex);
+ if (selectedChild != null) {
+ int targetScrollX = selectedChild.getLeft() + positionOffset;
+
+ if (tabIndex > 0 || positionOffset > 0) {
+ // If we're not at the first child and are mid-scroll, make sure we obey the offset
+ targetScrollX -= mTitleOffset;
+ }
+
+ scrollTo(targetScrollX, 0);
+ }
+ }
+
+ private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onViewPagerPageChanged(position, positionOffset);
+
+ View selectedTitle = mTabStrip.getChildAt(position);
+ int extraOffset = (selectedTitle != null)
+ ? (int) (positionOffset * selectedTitle.getWidth())
+ : 0;
+ scrollToTab(position, extraOffset);
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
+ positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mTabStrip.onViewPagerPageChanged(position, 0f);
+ scrollToTab(position, 0);
+ }
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageSelected(position);
+ }
+ }
+
+ }
+
+ private class TabClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ if (v == mTabStrip.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabStrip.java b/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabStrip.java
new file mode 100644
index 000000000..d5bbbae59
--- /dev/null
+++ b/samples/browseable/BasicTransition/src/com.example.android.common/view/SlidingTabStrip.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.common.view;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class SlidingTabStrip extends LinearLayout {
+
+ private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
+ private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
+ private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
+ private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
+
+ private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
+ private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
+ private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
+
+ private final int mBottomBorderThickness;
+ private final Paint mBottomBorderPaint;
+
+ private final int mSelectedIndicatorThickness;
+ private final Paint mSelectedIndicatorPaint;
+
+ private final int mDefaultBottomBorderColor;
+
+ private final Paint mDividerPaint;
+ private final float mDividerHeight;
+
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ private SlidingTabLayout.TabColorizer mCustomTabColorizer;
+ private final SimpleTabColorizer mDefaultTabColorizer;
+
+ SlidingTabStrip(Context context) {
+ this(context, null);
+ }
+
+ SlidingTabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+
+ final float density = getResources().getDisplayMetrics().density;
+
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
+ final int themeForegroundColor = outValue.data;
+
+ mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
+ DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
+
+ mDefaultTabColorizer = new SimpleTabColorizer();
+ mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
+ mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
+ DEFAULT_DIVIDER_COLOR_ALPHA));
+
+ mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
+ mBottomBorderPaint = new Paint();
+ mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
+
+ mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
+ mSelectedIndicatorPaint = new Paint();
+
+ mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
+ mDividerPaint = new Paint();
+ mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
+ }
+
+ void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
+ mCustomTabColorizer = customTabColorizer;
+ invalidate();
+ }
+
+ void setSelectedIndicatorColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setIndicatorColors(colors);
+ invalidate();
+ }
+
+ void setDividerColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setDividerColors(colors);
+ invalidate();
+ }
+
+ void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
+ final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
+ ? mCustomTabColorizer
+ : mDefaultTabColorizer;
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ int right = selectedTitle.getRight();
+ int color = tabColorizer.getIndicatorColor(mSelectedPosition);
+
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
+ if (color != nextColor) {
+ color = blendColors(nextColor, color, mSelectionOffset);
+ }
+
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+
+ mSelectedIndicatorPaint.setColor(color);
+
+ canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
+ height, mSelectedIndicatorPaint);
+ }
+
+ // Thin underline along the entire bottom edge
+ canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
+
+ // Vertical separators between the titles
+ int separatorTop = (height - dividerHeightPx) / 2;
+ for (int i = 0; i < childCount - 1; i++) {
+ View child = getChildAt(i);
+ mDividerPaint.setColor(tabColorizer.getDividerColor(i));
+ canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
+ separatorTop + dividerHeightPx, mDividerPaint);
+ }
+ }
+
+ /**
+ * Set the alpha value of the {@code color} to be the given {@code alpha} value.
+ */
+ private static int setColorAlpha(int color, byte alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Blend {@code color1} and {@code color2} using the given ratio.
+ *
+ * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
+ * 0.0 will return {@code color2}.
+ */
+ private static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRation = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
+ private int[] mIndicatorColors;
+ private int[] mDividerColors;
+
+ @Override
+ public final int getIndicatorColor(int position) {
+ return mIndicatorColors[position % mIndicatorColors.length];
+ }
+
+ @Override
+ public final int getDividerColor(int position) {
+ return mDividerColors[position % mDividerColors.length];
+ }
+
+ void setIndicatorColors(int... colors) {
+ mIndicatorColors = colors;
+ }
+
+ void setDividerColors(int... colors) {
+ mDividerColors = colors;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/DisplayingBitmaps/AndroidManifest.xml b/samples/browseable/DisplayingBitmaps/AndroidManifest.xml
new file mode 100644
index 000000000..23db308a3
--- /dev/null
+++ b/samples/browseable/DisplayingBitmaps/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ + This is a sample application for the Android Training class + "Displaying Bitmaps Efficiently" + (http://developer.android.com/training/displaying-bitmaps/).\n\n + + It demonstrates how to load large bitmaps efficiently off the main UI thread, caching + bitmaps (both in memory and on disk), managing bitmap memory and displaying bitmaps + in UI elements such as ViewPager and ListView/GridView. + +
diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/ic_launcher.png b/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..75b3c9781 Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/tile.9.png b/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-mdpi/ic_launcher.png b/samples/browseable/DisplayingBitmaps/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..0c9c11af9 Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-nodpi/empty_photo.png b/samples/browseable/DisplayingBitmaps/res/drawable-nodpi/empty_photo.png new file mode 100644 index 000000000..da1478a51 Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-nodpi/empty_photo.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/DisplayingBitmaps/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..7c5aeed04 Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/DisplayingBitmaps/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..91b0f960b Binary files /dev/null and b/samples/browseable/DisplayingBitmaps/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/DisplayingBitmaps/res/drawable/photogrid_list_selector.xml b/samples/browseable/DisplayingBitmaps/res/drawable/photogrid_list_selector.xml new file mode 100644 index 000000000..d331c3991 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/res/drawable/photogrid_list_selector.xml @@ -0,0 +1,33 @@ + + +When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.
+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogFragment.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogFragment.java new file mode 100644 index 000000000..b302acd4b --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogNode.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogNode.java new file mode 100644 index 000000000..bc37cabc0 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogView.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogView.java new file mode 100644 index 000000000..c01542b91 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogWrapper.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogWrapper.java new file mode 100644 index 000000000..16a9e7ba2 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/MessageOnlyLogFilter.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/MessageOnlyLogFilter.java new file mode 100644 index 000000000..19967dcd4 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.common.logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/provider/Images.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/provider/Images.java new file mode 100644 index 000000000..5a895462d --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/provider/Images.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.provider; + +/** + * Some simple test data to use for this sample app. + */ +public class Images { + + /** + * This are PicasaWeb URLs and could potentially change. Ideally the PicasaWeb API should be + * used to fetch the URLs. + * + * Credit to Romain Guy for the photos: + * http://www.curious-creature.org/ + * https://plus.google.com/109538161516040592207/about + * http://www.flickr.com/photos/romainguy + */ + public final static String[] imageUrls = new String[] { + "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg", + "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s1024/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg", + "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s1024/Another%252520Rockaway%252520Sunset.jpg", + "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s1024/Antelope%252520Butte.jpg", + "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s1024/Antelope%252520Hallway.jpg", + "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s1024/Antelope%252520Walls.jpg", + "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s1024/Apre%2525CC%252580s%252520la%252520Pluie.jpg", + "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s1024/Backlit%252520Cloud.jpg", + "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s1024/Bee%252520and%252520Flower.jpg", + "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s1024/Bonzai%252520Rock%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s1024/Caterpillar.jpg", + "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s1024/Chess.jpg", + "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s1024/Chihuly.jpg", + "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s1024/Closed%252520Door.jpg", + "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s1024/Colorado%252520River%252520Sunset.jpg", + "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s1024/Colors%252520of%252520Autumn.jpg", + "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s1024/Countryside.jpg", + "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s1024/Death%252520Valley%252520-%252520Dunes.jpg", + "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s1024/Delicate%252520Arch.jpg", + "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s1024/Despair.jpg", + "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s1024/Eagle%252520Fall%252520Sunrise.jpg", + "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s1024/Electric%252520Storm.jpg", + "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s1024/False%252520Kiva.jpg", + "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s1024/Fitzgerald%252520Streaks.jpg", + "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s1024/Foggy%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s1024/Frantic.jpg", + "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s1024/Golden%252520Gate%252520Afternoon.jpg", + "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s1024/Golden%252520Gate%252520Fog.jpg", + "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s1024/Golden%252520Grass.jpg", + "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s1024/Grand%252520Teton.jpg", + "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s1024/Grass%252520Closeup.jpg", + "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s1024/Green%252520Grass.jpg", + "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s1024/Hanging%252520Leaf.jpg", + "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s1024/Highway%2525201.jpg", + "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s1024/Horseshoe%252520Bend%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s1024/Horseshoe%252520Bend.jpg", + "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s1024/Into%252520the%252520Blue.jpg", + "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s1024/Jelly%252520Fish%2525202.jpg", + "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s1024/Jelly%252520Fish%2525203.jpg", + "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s1024/Kauai.jpg", + "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s1024/Kyoto%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s1024/Lake%252520Tahoe%252520Colors.jpg", + "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s1024/Lava%252520from%252520the%252520Sky.jpg", + "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s1024/Leica%25252050mm%252520Summilux.jpg", + "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s1024/Leica%25252050mm%252520Summilux.jpg", + "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s1024/Leica%252520M8%252520%252528Front%252529.jpg", + "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s1024/Light%252520to%252520Sand.jpg", + "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s1024/Little%252520Bit%252520of%252520Paradise.jpg", + "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s1024/Lone%252520Pine%252520Sunset.jpg", + "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s1024/Lonely%252520Rock.jpg", + "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s1024/Longue%252520Vue.jpg", + "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s1024/Look%252520Me%252520in%252520the%252520Eye.jpg", + "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s1024/Lost%252520in%252520a%252520Field.jpg", + "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s1024/Marshall%252520Beach%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s1024/Mono%252520Lake%252520Blue.jpg", + "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s1024/Monument%252520Valley%252520Overlook.jpg", + "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s1024/Moving%252520Rock.jpg", + "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s1024/Napali%252520Coast.jpg", + "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s1024/One%252520Wheel.jpg", + "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s1024/Open%252520Sky.jpg", + "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s1024/Orange%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s1024/Orchid.jpg", + "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s1024/Over%252520there.jpg", + "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s1024/Plumes.jpg", + "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s1024/Rainbokeh.jpg", + "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s1024/Rainbow.jpg", + "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s1024/Rice%252520Fields.jpg", + "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s1024/Rockaway%252520Fire%252520Sky.jpg", + "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s1024/Rockaway%252520Flow.jpg", + "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s1024/Rockaway%252520Sunset%252520Sky.jpg", + "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s1024/Russian%252520Ridge%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s1024/Rust%252520Knot.jpg", + "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s1024/Sailing%252520Stones.jpg", + "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s1024/Seahorse.jpg", + "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s1024/Shinjuku%252520Street.jpg", + "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s1024/Sierra%252520Heavens.jpg", + "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s1024/Sierra%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s1024/Sin%252520Lights.jpg", + "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s1024/Starry%252520Lake.jpg", + "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s1024/Starry%252520Night.jpg", + "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s1024/Stream.jpg", + "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s1024/Strip%252520Sunset.jpg", + "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s1024/Sunset%252520Hills.jpg", + "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s1024/Tenaya%252520Lake%2525202.jpg", + "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s1024/Tenaya%252520Lake.jpg", + "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s1024/The%252520Cave%252520BW.jpg", + "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s1024/The%252520Fisherman.jpg", + "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s1024/The%252520Night%252520is%252520Coming.jpg", + "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s1024/The%252520Road.jpg", + "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s1024/Tokyo%252520Heights.jpg", + "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s1024/Tokyo%252520Highway.jpg", + "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s1024/Tokyo%252520Smog.jpg", + "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s1024/Tufa%252520at%252520Night.jpg", + "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s1024/Valley%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s1024/Windmill%252520Sunrise.jpg", + "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s1024/Windmill.jpg", + "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s1024/Windmills.jpg", + "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s1024/Yet%252520Another%252520Rockaway%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s1024/Yosemite%252520Tree.jpg", + }; + + /** + * This are PicasaWeb thumbnail URLs and could potentially change. Ideally the PicasaWeb API + * should be used to fetch the URLs. + * + * Credit to Romain Guy for the photos: + * http://www.curious-creature.org/ + * https://plus.google.com/109538161516040592207/about + * http://www.flickr.com/photos/romainguy + */ + public final static String[] imageThumbUrls = new String[] { + "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s240-c/A%252520Photographer.jpg", + "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s240-c/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg", + "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s240-c/Another%252520Rockaway%252520Sunset.jpg", + "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s240-c/Antelope%252520Butte.jpg", + "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s240-c/Antelope%252520Hallway.jpg", + "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s240-c/Antelope%252520Walls.jpg", + "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s240-c/Apre%2525CC%252580s%252520la%252520Pluie.jpg", + "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s240-c/Backlit%252520Cloud.jpg", + "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s240-c/Bee%252520and%252520Flower.jpg", + "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s240-c/Bonzai%252520Rock%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s240-c/Caterpillar.jpg", + "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s240-c/Chess.jpg", + "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s240-c/Chihuly.jpg", + "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s240-c/Closed%252520Door.jpg", + "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s240-c/Colorado%252520River%252520Sunset.jpg", + "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s240-c/Colors%252520of%252520Autumn.jpg", + "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s240-c/Countryside.jpg", + "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s240-c/Death%252520Valley%252520-%252520Dunes.jpg", + "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s240-c/Delicate%252520Arch.jpg", + "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s240-c/Despair.jpg", + "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s240-c/Eagle%252520Fall%252520Sunrise.jpg", + "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s240-c/Electric%252520Storm.jpg", + "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s240-c/False%252520Kiva.jpg", + "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s240-c/Fitzgerald%252520Streaks.jpg", + "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s240-c/Foggy%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s240-c/Frantic.jpg", + "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s240-c/Golden%252520Gate%252520Afternoon.jpg", + "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s240-c/Golden%252520Gate%252520Fog.jpg", + "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s240-c/Golden%252520Grass.jpg", + "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s240-c/Grand%252520Teton.jpg", + "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s240-c/Grass%252520Closeup.jpg", + "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s240-c/Green%252520Grass.jpg", + "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s240-c/Hanging%252520Leaf.jpg", + "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s240-c/Highway%2525201.jpg", + "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s240-c/Horseshoe%252520Bend%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s240-c/Horseshoe%252520Bend.jpg", + "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s240-c/Into%252520the%252520Blue.jpg", + "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s240-c/Jelly%252520Fish%2525202.jpg", + "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s240-c/Jelly%252520Fish%2525203.jpg", + "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s240-c/Kauai.jpg", + "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s240-c/Kyoto%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s240-c/Lake%252520Tahoe%252520Colors.jpg", + "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s240-c/Lava%252520from%252520the%252520Sky.jpg", + "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s240-c/Leica%25252050mm%252520Summilux.jpg", + "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s240-c/Leica%25252050mm%252520Summilux.jpg", + "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s240-c/Leica%252520M8%252520%252528Front%252529.jpg", + "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s240-c/Light%252520to%252520Sand.jpg", + "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s240-c/Little%252520Bit%252520of%252520Paradise.jpg", + "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s240-c/Lone%252520Pine%252520Sunset.jpg", + "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s240-c/Lonely%252520Rock.jpg", + "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s240-c/Longue%252520Vue.jpg", + "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s240-c/Look%252520Me%252520in%252520the%252520Eye.jpg", + "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s240-c/Lost%252520in%252520a%252520Field.jpg", + "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s240-c/Marshall%252520Beach%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s240-c/Mono%252520Lake%252520Blue.jpg", + "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s240-c/Monument%252520Valley%252520Overlook.jpg", + "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s240-c/Moving%252520Rock.jpg", + "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s240-c/Napali%252520Coast.jpg", + "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s240-c/One%252520Wheel.jpg", + "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s240-c/Open%252520Sky.jpg", + "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s240-c/Orange%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s240-c/Orchid.jpg", + "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s240-c/Over%252520there.jpg", + "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s240-c/Plumes.jpg", + "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s240-c/Rainbokeh.jpg", + "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s240-c/Rainbow.jpg", + "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s240-c/Rice%252520Fields.jpg", + "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s240-c/Rockaway%252520Fire%252520Sky.jpg", + "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s240-c/Rockaway%252520Flow.jpg", + "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s240-c/Rockaway%252520Sunset%252520Sky.jpg", + "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s240-c/Russian%252520Ridge%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s240-c/Rust%252520Knot.jpg", + "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s240-c/Sailing%252520Stones.jpg", + "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s240-c/Seahorse.jpg", + "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s240-c/Shinjuku%252520Street.jpg", + "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s240-c/Sierra%252520Heavens.jpg", + "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s240-c/Sierra%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s240-c/Sin%252520Lights.jpg", + "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s240-c/Starry%252520Lake.jpg", + "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s240-c/Starry%252520Night.jpg", + "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s240-c/Stream.jpg", + "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s240-c/Strip%252520Sunset.jpg", + "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s240-c/Sunset%252520Hills.jpg", + "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s240-c/Tenaya%252520Lake%2525202.jpg", + "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s240-c/Tenaya%252520Lake.jpg", + "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s240-c/The%252520Cave%252520BW.jpg", + "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s240-c/The%252520Fisherman.jpg", + "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s240-c/The%252520Night%252520is%252520Coming.jpg", + "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s240-c/The%252520Road.jpg", + "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s240-c/Tokyo%252520Heights.jpg", + "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s240-c/Tokyo%252520Highway.jpg", + "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s240-c/Tokyo%252520Smog.jpg", + "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s240-c/Tufa%252520at%252520Night.jpg", + "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s240-c/Valley%252520Sunset.jpg", + "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s240-c/Windmill%252520Sunrise.jpg", + "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s240-c/Windmill.jpg", + "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s240-c/Windmills.jpg", + "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s240-c/Yet%252520Another%252520Rockaway%252520Sunset.jpg", + "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s240-c/Yosemite%252520Tree.jpg", + }; +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailActivity.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailActivity.java new file mode 100644 index 000000000..c2be1acfc --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailActivity.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.ui; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.app.NavUtils; +import android.support.v4.view.ViewPager; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager.LayoutParams; +import android.widget.Toast; + +import com.example.android.displayingbitmaps.BuildConfig; +import com.example.android.displayingbitmaps.R; +import com.example.android.displayingbitmaps.provider.Images; +import com.example.android.displayingbitmaps.util.ImageCache; +import com.example.android.displayingbitmaps.util.ImageFetcher; +import com.example.android.displayingbitmaps.util.Utils; + +public class ImageDetailActivity extends FragmentActivity implements OnClickListener { + private static final String IMAGE_CACHE_DIR = "images"; + public static final String EXTRA_IMAGE = "extra_image"; + + private ImagePagerAdapter mAdapter; + private ImageFetcher mImageFetcher; + private ViewPager mPager; + + @TargetApi(VERSION_CODES.HONEYCOMB) + @Override + public void onCreate(Bundle savedInstanceState) { + if (BuildConfig.DEBUG) { + Utils.enableStrictMode(); + } + super.onCreate(savedInstanceState); + setContentView(R.layout.image_detail_pager); + + // Fetch screen height and width, to use as our max size when loading images as this + // activity runs full screen + final DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + final int height = displayMetrics.heightPixels; + final int width = displayMetrics.widthPixels; + + // For this sample we'll use half of the longest width to resize our images. As the + // image scaling ensures the image is larger than this, we should be left with a + // resolution that is appropriate for both portrait and landscape. For best image quality + // we shouldn't divide by 2, but this will use more memory and require a larger memory + // cache. + final int longest = (height > width ? height : width) / 2; + + ImageCache.ImageCacheParams cacheParams = + new ImageCache.ImageCacheParams(this, IMAGE_CACHE_DIR); + cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory + + // The ImageFetcher takes care of loading images into our ImageView children asynchronously + mImageFetcher = new ImageFetcher(this, longest); + mImageFetcher.addImageCache(getSupportFragmentManager(), cacheParams); + mImageFetcher.setImageFadeIn(false); + + // Set up ViewPager and backing adapter + mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), Images.imageUrls.length); + mPager = (ViewPager) findViewById(R.id.pager); + mPager.setAdapter(mAdapter); + mPager.setPageMargin((int) getResources().getDimension(R.dimen.horizontal_page_margin)); + mPager.setOffscreenPageLimit(2); + + // Set up activity to go full screen + getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN); + + // Enable some additional newer visibility and ActionBar features to create a more + // immersive photo viewing experience + if (Utils.hasHoneycomb()) { + final ActionBar actionBar = getActionBar(); + + // Hide title text and set home as up + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setDisplayHomeAsUpEnabled(true); + + // Hide and show the ActionBar as the visibility changes + mPager.setOnSystemUiVisibilityChangeListener( + new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int vis) { + if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { + actionBar.hide(); + } else { + actionBar.show(); + } + } + }); + + // Start low profile mode and hide ActionBar + mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + actionBar.hide(); + } + + // Set the current item based on the extra passed in to this activity + final int extraCurrentItem = getIntent().getIntExtra(EXTRA_IMAGE, -1); + if (extraCurrentItem != -1) { + mPager.setCurrentItem(extraCurrentItem); + } + } + + @Override + public void onResume() { + super.onResume(); + mImageFetcher.setExitTasksEarly(false); + } + + @Override + protected void onPause() { + super.onPause(); + mImageFetcher.setExitTasksEarly(true); + mImageFetcher.flushCache(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mImageFetcher.closeCache(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + case R.id.clear_cache: + mImageFetcher.clearCache(); + Toast.makeText( + this, R.string.clear_cache_complete_toast,Toast.LENGTH_SHORT).show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + /** + * Called by the ViewPager child fragments to load images via the one ImageFetcher + */ + public ImageFetcher getImageFetcher() { + return mImageFetcher; + } + + /** + * The main adapter that backs the ViewPager. A subclass of FragmentStatePagerAdapter as there + * could be a large number of items in the ViewPager and we don't want to retain them all in + * memory at once but create/destroy them on the fly. + */ + private class ImagePagerAdapter extends FragmentStatePagerAdapter { + private final int mSize; + + public ImagePagerAdapter(FragmentManager fm, int size) { + super(fm); + mSize = size; + } + + @Override + public int getCount() { + return mSize; + } + + @Override + public Fragment getItem(int position) { + return ImageDetailFragment.newInstance(Images.imageUrls[position]); + } + } + + /** + * Set on the ImageView in the ViewPager children fragments, to enable/disable low profile mode + * when the ImageView is touched. + */ + @TargetApi(VERSION_CODES.HONEYCOMB) + @Override + public void onClick(View v) { + final int vis = mPager.getSystemUiVisibility(); + if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { + mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } else { + mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailFragment.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailFragment.java new file mode 100644 index 000000000..506729a7e --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageDetailFragment.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.example.android.displayingbitmaps.R; +import com.example.android.displayingbitmaps.util.ImageFetcher; +import com.example.android.displayingbitmaps.util.ImageWorker; +import com.example.android.displayingbitmaps.util.Utils; + +/** + * This fragment will populate the children of the ViewPager from {@link ImageDetailActivity}. + */ +public class ImageDetailFragment extends Fragment { + private static final String IMAGE_DATA_EXTRA = "extra_image_data"; + private String mImageUrl; + private ImageView mImageView; + private ImageFetcher mImageFetcher; + + /** + * Factory method to generate a new instance of the fragment given an image number. + * + * @param imageUrl The image url to load + * @return A new instance of ImageDetailFragment with imageNum extras + */ + public static ImageDetailFragment newInstance(String imageUrl) { + final ImageDetailFragment f = new ImageDetailFragment(); + + final Bundle args = new Bundle(); + args.putString(IMAGE_DATA_EXTRA, imageUrl); + f.setArguments(args); + + return f; + } + + /** + * Empty constructor as per the Fragment documentation + */ + public ImageDetailFragment() {} + + /** + * Populate image using a url from extras, use the convenience factory method + * {@link ImageDetailFragment#newInstance(String)} to create this fragment. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageUrl = getArguments() != null ? getArguments().getString(IMAGE_DATA_EXTRA) : null; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate and locate the main ImageView + final View v = inflater.inflate(R.layout.image_detail_fragment, container, false); + mImageView = (ImageView) v.findViewById(R.id.imageView); + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Use the parent activity to load the image asynchronously into the ImageView (so a single + // cache can be used over all pages in the ViewPager + if (ImageDetailActivity.class.isInstance(getActivity())) { + mImageFetcher = ((ImageDetailActivity) getActivity()).getImageFetcher(); + mImageFetcher.loadImage(mImageUrl, mImageView); + } + + // Pass clicks on the ImageView to the parent activity to handle + if (OnClickListener.class.isInstance(getActivity()) && Utils.hasHoneycomb()) { + mImageView.setOnClickListener((OnClickListener) getActivity()); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mImageView != null) { + // Cancel any pending image work + ImageWorker.cancelWork(mImageView); + mImageView.setImageDrawable(null); + } + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridActivity.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridActivity.java new file mode 100644 index 000000000..f171955ae --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridActivity.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.ui; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; + +import com.example.android.displayingbitmaps.BuildConfig; +import com.example.android.displayingbitmaps.util.Utils; + +/** + * Simple FragmentActivity to hold the main {@link ImageGridFragment} and not much else. + */ +public class ImageGridActivity extends FragmentActivity { + private static final String TAG = "ImageGridActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (BuildConfig.DEBUG) { + Utils.enableStrictMode(); + } + super.onCreate(savedInstanceState); + + if (getSupportFragmentManager().findFragmentByTag(TAG) == null) { + final FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.add(android.R.id.content, new ImageGridFragment(), TAG); + ft.commit(); + } + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridFragment.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridFragment.java new file mode 100644 index 000000000..4eb1f1b35 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/ImageGridFragment.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.ui; + +import android.annotation.TargetApi; +import android.app.ActivityOptions; +import android.content.Context; +import android.content.Intent; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.Toast; + +import com.example.android.common.logger.Log; +import com.example.android.displayingbitmaps.BuildConfig; +import com.example.android.displayingbitmaps.R; +import com.example.android.displayingbitmaps.provider.Images; +import com.example.android.displayingbitmaps.util.ImageCache; +import com.example.android.displayingbitmaps.util.ImageFetcher; +import com.example.android.displayingbitmaps.util.Utils; + +/** + * The main fragment that powers the ImageGridActivity screen. Fairly straight forward GridView + * implementation with the key addition being the ImageWorker class w/ImageCache to load children + * asynchronously, keeping the UI nice and smooth and caching thumbnails for quick retrieval. The + * cache is retained over configuration changes like orientation change so the images are populated + * quickly if, for example, the user rotates the device. + */ +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + private static final String TAG = "ImageGridFragment"; + private static final String IMAGE_CACHE_DIR = "thumbs"; + + private int mImageThumbSize; + private int mImageThumbSpacing; + private ImageAdapter mAdapter; + private ImageFetcher mImageFetcher; + + /** + * Empty constructor as per the Fragment documentation + */ + public ImageGridFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + mImageThumbSize = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size); + mImageThumbSpacing = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing); + + mAdapter = new ImageAdapter(getActivity()); + + ImageCache.ImageCacheParams cacheParams = + new ImageCache.ImageCacheParams(getActivity(), IMAGE_CACHE_DIR); + + cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory + + // The ImageFetcher takes care of loading images into our ImageView children asynchronously + mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize); + mImageFetcher.setLoadingImage(R.drawable.empty_photo); + mImageFetcher.addImageCache(getActivity().getSupportFragmentManager(), cacheParams); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + final View v = inflater.inflate(R.layout.image_grid_fragment, container, false); + final GridView mGridView = (GridView) v.findViewById(R.id.gridView); + mGridView.setAdapter(mAdapter); + mGridView.setOnItemClickListener(this); + mGridView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView absListView, int scrollState) { + // Pause fetcher to ensure smoother scrolling when flinging + if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { + // Before Honeycomb pause image loading on scroll to help with performance + if (!Utils.hasHoneycomb()) { + mImageFetcher.setPauseWork(true); + } + } else { + mImageFetcher.setPauseWork(false); + } + } + + @Override + public void onScroll(AbsListView absListView, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + } + }); + + // This listener is used to get the final width of the GridView and then calculate the + // number of columns and the width of each column. The width of each column is variable + // as the GridView has stretchMode=columnWidth. The column width is used to set the height + // of each view so we get nice square thumbnails. + mGridView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @TargetApi(VERSION_CODES.JELLY_BEAN) + @Override + public void onGlobalLayout() { + if (mAdapter.getNumColumns() == 0) { + final int numColumns = (int) Math.floor( + mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing)); + if (numColumns > 0) { + final int columnWidth = + (mGridView.getWidth() / numColumns) - mImageThumbSpacing; + mAdapter.setNumColumns(numColumns); + mAdapter.setItemHeight(columnWidth); + if (BuildConfig.DEBUG) { + Log.d(TAG, "onCreateView - numColumns set to " + numColumns); + } + if (Utils.hasJellyBean()) { + mGridView.getViewTreeObserver() + .removeOnGlobalLayoutListener(this); + } else { + mGridView.getViewTreeObserver() + .removeGlobalOnLayoutListener(this); + } + } + } + } + }); + + return v; + } + + @Override + public void onResume() { + super.onResume(); + mImageFetcher.setExitTasksEarly(false); + mAdapter.notifyDataSetChanged(); + } + + @Override + public void onPause() { + super.onPause(); + mImageFetcher.setPauseWork(false); + mImageFetcher.setExitTasksEarly(true); + mImageFetcher.flushCache(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mImageFetcher.closeCache(); + } + + @TargetApi(VERSION_CODES.JELLY_BEAN) + @Override + public void onItemClick(AdapterView> parent, View v, int position, long id) { + final Intent i = new Intent(getActivity(), ImageDetailActivity.class); + i.putExtra(ImageDetailActivity.EXTRA_IMAGE, (int) id); + if (Utils.hasJellyBean()) { + // makeThumbnailScaleUpAnimation() looks kind of ugly here as the loading spinner may + // show plus the thumbnail image in GridView is cropped. so using + // makeScaleUpAnimation() instead. + ActivityOptions options = + ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight()); + getActivity().startActivity(i, options.toBundle()); + } else { + startActivity(i); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.main_menu, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.clear_cache: + mImageFetcher.clearCache(); + Toast.makeText(getActivity(), R.string.clear_cache_complete_toast, + Toast.LENGTH_SHORT).show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * The main adapter that backs the GridView. This is fairly standard except the number of + * columns in the GridView is used to create a fake top row of empty views as we use a + * transparent ActionBar and don't want the real top row of images to start off covered by it. + */ + private class ImageAdapter extends BaseAdapter { + + private final Context mContext; + private int mItemHeight = 0; + private int mNumColumns = 0; + private int mActionBarHeight = 0; + private GridView.LayoutParams mImageViewLayoutParams; + + public ImageAdapter(Context context) { + super(); + mContext = context; + mImageViewLayoutParams = new GridView.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + // Calculate ActionBar height + TypedValue tv = new TypedValue(); + if (context.getTheme().resolveAttribute( + android.R.attr.actionBarSize, tv, true)) { + mActionBarHeight = TypedValue.complexToDimensionPixelSize( + tv.data, context.getResources().getDisplayMetrics()); + } + } + + @Override + public int getCount() { + // If columns have yet to be determined, return no items + if (getNumColumns() == 0) { + return 0; + } + + // Size + number of columns for top empty row + return Images.imageThumbUrls.length + mNumColumns; + } + + @Override + public Object getItem(int position) { + return position < mNumColumns ? + null : Images.imageThumbUrls[position - mNumColumns]; + } + + @Override + public long getItemId(int position) { + return position < mNumColumns ? 0 : position - mNumColumns; + } + + @Override + public int getViewTypeCount() { + // Two types of views, the normal ImageView and the top row of empty views + return 2; + } + + @Override + public int getItemViewType(int position) { + return (position < mNumColumns) ? 1 : 0; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup container) { + //BEGIN_INCLUDE(load_gridview_item) + // First check if this is the top row + if (position < mNumColumns) { + if (convertView == null) { + convertView = new View(mContext); + } + // Set empty view with height of ActionBar + convertView.setLayoutParams(new AbsListView.LayoutParams( + LayoutParams.MATCH_PARENT, mActionBarHeight)); + return convertView; + } + + // Now handle the main ImageView thumbnails + ImageView imageView; + if (convertView == null) { // if it's not recycled, instantiate and initialize + imageView = new RecyclingImageView(mContext); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(mImageViewLayoutParams); + } else { // Otherwise re-use the converted view + imageView = (ImageView) convertView; + } + + // Check the height matches our calculated column width + if (imageView.getLayoutParams().height != mItemHeight) { + imageView.setLayoutParams(mImageViewLayoutParams); + } + + // Finally load the image asynchronously into the ImageView, this also takes care of + // setting a placeholder image while the background thread runs + mImageFetcher.loadImage(Images.imageThumbUrls[position - mNumColumns], imageView); + return imageView; + //END_INCLUDE(load_gridview_item) + } + + /** + * Sets the item height. Useful for when we know the column width so the height can be set + * to match. + * + * @param height + */ + public void setItemHeight(int height) { + if (height == mItemHeight) { + return; + } + mItemHeight = height; + mImageViewLayoutParams = + new GridView.LayoutParams(LayoutParams.MATCH_PARENT, mItemHeight); + mImageFetcher.setImageSize(height); + notifyDataSetChanged(); + } + + public void setNumColumns(int numColumns) { + mNumColumns = numColumns; + } + + public int getNumColumns() { + return mNumColumns; + } + } +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/RecyclingImageView.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/RecyclingImageView.java new file mode 100644 index 000000000..1db134c31 --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/ui/RecyclingImageView.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.example.android.displayingbitmaps.util.RecyclingBitmapDrawable; + +/** + * Sub-class of ImageView which automatically notifies the drawable when it is + * being displayed. + */ +public class RecyclingImageView extends ImageView { + + public RecyclingImageView(Context context) { + super(context); + } + + public RecyclingImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * @see android.widget.ImageView#onDetachedFromWindow() + */ + @Override + protected void onDetachedFromWindow() { + // This has been detached from Window, so clear the drawable + setImageDrawable(null); + + super.onDetachedFromWindow(); + } + + /** + * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable) + */ + @Override + public void setImageDrawable(Drawable drawable) { + // Keep hold of previous Drawable + final Drawable previousDrawable = getDrawable(); + + // Call super to set new Drawable + super.setImageDrawable(drawable); + + // Notify new Drawable that it is being displayed + notifyDrawable(drawable, true); + + // Notify old Drawable so it is no longer being displayed + notifyDrawable(previousDrawable, false); + } + + /** + * Notifies the drawable that it's displayed state has changed. + * + * @param drawable + * @param isDisplayed + */ + private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) { + if (drawable instanceof RecyclingBitmapDrawable) { + // The drawable is a CountingBitmapDrawable, so notify it + ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed); + } else if (drawable instanceof LayerDrawable) { + // The drawable is a LayerDrawable, so recurse on each layer + LayerDrawable layerDrawable = (LayerDrawable) drawable; + for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) { + notifyDrawable(layerDrawable.getDrawable(i), isDisplayed); + } + } + } + +} diff --git a/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/AsyncTask.java b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/AsyncTask.java new file mode 100644 index 000000000..dffade48e --- /dev/null +++ b/samples/browseable/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/AsyncTask.java @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.displayingbitmaps.util; + +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.Message; +import android.os.Process; + +import java.util.ArrayDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ************************************* + * Copied from JB release framework: + * https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/os/AsyncTask.java + * + * so that threading behavior on all OS versions is the same and we can tweak behavior by using + * executeOnExecutor() if needed. + * + * There are 3 changes in this copy of AsyncTask: + * -pre-HC a single thread executor is used for serial operation + * (Executors.newSingleThreadExecutor) and is the default + * -the default THREAD_POOL_EXECUTOR was changed to use DiscardOldestPolicy + * -a new fixed thread pool called DUAL_THREAD_EXECUTOR was added + * ************************************* + * + *AsyncTask enables proper and easy use of the UI thread. This class allows to + * perform background operations and publish results on the UI thread without + * having to manipulate threads and/or handlers.
+ * + *AsyncTask is designed to be a helper class around {@link Thread} and {@link android.os.Handler}
+ * and does not constitute a generic threading framework. AsyncTasks should ideally be
+ * used for short operations (a few seconds at the most.) If you need to keep threads
+ * running for long periods of time, it is highly recommended you use the various APIs
+ * provided by the java.util.concurrent pacakge such as {@link java.util.concurrent.Executor},
+ * {@link java.util.concurrent.ThreadPoolExecutor} and {@link java.util.concurrent.FutureTask}.
An asynchronous task is defined by a computation that runs on a background thread and
+ * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
+ * types, called Params, Progress and Result,
+ * and 4 steps, called onPreExecute, doInBackground,
+ * onProgressUpdate and onPostExecute.
For more information about using tasks and threads, read the + * Processes and + * Threads developer guide.
+ *AsyncTask must be subclassed to be used. The subclass will override at least + * one method ({@link #doInBackground}), and most often will override a + * second one ({@link #onPostExecute}.)
+ * + *Here is an example of subclassing:
+ *
+ * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
+ * protected Long doInBackground(URL... urls) {
+ * int count = urls.length;
+ * long totalSize = 0;
+ * for (int i = 0; i < count; i++) {
+ * totalSize += Downloader.downloadFile(urls[i]);
+ * publishProgress((int) ((i / (float) count) * 100));
+ * // Escape early if cancel() is called
+ * if (isCancelled()) break;
+ * }
+ * return totalSize;
+ * }
+ *
+ * protected void onProgressUpdate(Integer... progress) {
+ * setProgressPercent(progress[0]);
+ * }
+ *
+ * protected void onPostExecute(Long result) {
+ * showDialog("Downloaded " + result + " bytes");
+ * }
+ * }
+ *
+ *
+ * Once created, a task is executed very simply:
+ *+ * new DownloadFilesTask().execute(url1, url2, url3); + *+ * + *
The three types used by an asynchronous task are the following:
+ *Params, the type of the parameters sent to the task upon
+ * execution.Progress, the type of the progress units published during
+ * the background computation.Result, the type of the result of the background
+ * computation.Not all types are always used by an asynchronous task. To mark a type as unused, + * simply use the type {@link Void}:
+ *
+ * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
+ *
+ *
+ * When an asynchronous task is executed, the task goes through 4 steps:
+ *A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking + * this method will cause subsequent calls to {@link #isCancelled()} to return true. + * After invoking this method, {@link #onCancelled(Object)}, instead of + * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} + * returns. To ensure that a task is cancelled as quickly as possible, you should always + * check the return value of {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)
+ * + *There are a few threading rules that must be followed for this class to + * work properly:
+ *AsyncTask guarantees that all callback calls are synchronized in such a way that the following + * operations are safe without explicit synchronizations.
+ *When first introduced, AsyncTasks were executed serially on a single background + * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting with + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single + * thread to avoid common application errors caused by parallel execution.
+ *If you truly want parallel execution, you can invoke + * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with + * {@link #THREAD_POOL_EXECUTOR}.
+ */ +public abstract class AsyncTaskRuns on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.
+ * + *This method won't be invoked if the task was cancelled.
+ * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onPostExecute(Result result) { + } + + /** + * Runs on the UI thread after {@link #publishProgress} is invoked. + * The specified values are the values passed to {@link #publishProgress}. + * + * @param values The values indicating progress. + * + * @see #publishProgress + * @see #doInBackground + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onProgressUpdate(Progress... values) { + } + + /** + *Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.
+ * + *The default implementation simply invokes {@link #onCancelled()} and
+ * ignores the result. If you write your own implementation, do not call
+ * super.onCancelled(result).
Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.
+ * + *Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.
+ * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + protected void onCancelled() { + } + + /** + * Returns true if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground(Object[])} to end the task as soon as possible. + * + * @return true if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + *Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when cancel is called, + * this task should never run. If the task has already started, + * then the mayInterruptIfRunning parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.
+ * + *Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground(Object[])} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])} to finish the task as early as + * possible.
+ * + * @param mayInterruptIfRunning true if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return false if the task could not be cancelled, + * typically because it has already completed normally; + * true otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws java.util.concurrent.CancellationException If the computation was cancelled. + * @throws java.util.concurrent.ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + public final Result get() throws InterruptedException, ExecutionException { + return mFuture.get(); + } + + /** + * Waits if necessary for at most the given time for the computation + * to complete, and then retrieves its result. + * + * @param timeout Time to wait before cancelling the operation. + * @param unit The time unit for the timeout. + * + * @return The computed result. + * + * @throws java.util.concurrent.CancellationException If the computation was cancelled. + * @throws java.util.concurrent.ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + * @throws java.util.concurrent.TimeoutException If the wait timed out. + */ + public final Result get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return mFuture.get(timeout, unit); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *Note: this function schedules the task on a queue for a single background + * thread or pool of threads depending on the platform version. When first + * introduced, AsyncTasks were executed serially on a single background thread. + * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused + * by parallel execution. If you truly want parallel execution, you can use + * the {@link #executeOnExecutor} version of this method + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. + * + *
This method must be invoked on the UI thread.
+ *
+ * @param params The parameters of the task.
+ *
+ * @return This instance of AsyncTask.
+ *
+ * @throws IllegalStateException If {@link #getStatus()} returns either
+ * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ *
+ * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
+ * @see #execute(Runnable)
+ */
+ public final AsyncTask This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
+ * allow multiple tasks to run in parallel on a pool of threads managed by
+ * AsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom
+ * behavior.
+ *
+ * Warning: Allowing multiple tasks to run in parallel from
+ * a thread pool is generally not what one wants, because the order
+ * of their operation is not defined. For example, if these tasks are used
+ * to modify any state in common (such as writing a file due to a button click),
+ * there are no guarantees on the order of the modifications.
+ * Without careful work it is possible in rare cases for the newer version
+ * of the data to be over-written by an older one, leading to obscure data
+ * loss and stability issues. Such changes are best
+ * executed in serial; to guarantee such work is serialized regardless of
+ * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
+ *
+ * This method must be invoked on the UI thread.
+ *
+ * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
+ * convenient process-wide thread pool for tasks that are loosely coupled.
+ * @param params The parameters of the task.
+ *
+ * @return This instance of AsyncTask.
+ *
+ * @throws IllegalStateException If {@link #getStatus()} returns either
+ * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ *
+ * @see #execute(Object[])
+ */
+ public final AsyncTask