Java tutorial
/* * Copyright 2013 Phil Brown * * 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 self.philbrown.droidQuery; import java.io.File; import java.io.FileOutputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import java.util.TimerTask; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.http.client.methods.HttpUriRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import self.philbrown.css.CSSSelector; import self.philbrown.cssparser.TokenSequence; import self.philbrown.droidQuery.AjaxOptions.Redundancy; import self.philbrown.droidQuery.SwipeDetector.SwipeListener; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.FileObserver; import android.os.Handler; import android.text.Editable; import android.text.Html; import android.text.Spanned; import android.text.TextWatcher; import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnticipateInterpolator; import android.view.animation.AnticipateOvershootInterpolator; import android.view.animation.BounceInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; import android.webkit.URLUtil; import android.widget.AdapterView; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.TextView; import android.widget.Toast; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.Animator.AnimatorListener; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ObjectAnimator; import com.nineoldandroids.animation.ValueAnimator; import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener; import com.nineoldandroids.view.ViewHelper; /** * <h1>droidQuery</h1> * An <a href="https://github.com/github/android">Android</a> <i>port</i> of * <a href="https://github.com/jquery/jquery">jQuery</a>. Supports Android API 2.2 (Froyo) and up. * @author Phil Brown */ public class $ { /** * Data types for <em>ajax</em> request responses */ public static enum DataType { /** JavaScript Object Notation */ JSON, /** Extensible Markup Language */ XML, /** Textual response */ TEXT, /** Bourne Script response*/ SCRIPT, /** Bitmap response */ IMAGE }; /** * File locations used for {@code write} methods. * @see $#write(byte[], FileLocation, String, boolean, boolean) * @see $#write(byte[], FileLocation, String, boolean, boolean, Function, Function) */ public static enum FileLocation { /** * File Location constant. Writes files to the private directory * {@code /data/data/<package>}. Internal files cannot specify a file name with separators. */ INTERNAL, /** * File Location constant. Writes files to the external directory * {@code <external dir>/<package>} */ EXTERNAL, /** * File Location constant. Writes files to the private directory * {@code /data/data/<package>/cache}. Cache files can be deleted by the system when space is * needed. */ CACHE, /** * File Location constant. Writes files to the external directory * {@code <external dir>Android/data/<package>} */ DATA, /** * File Location constant. Writes files to the path given by {@code fileName}. */ CUSTOM }; /** * Relates to the interpolator used for <em>droidQuery</em> animations */ public static enum Easing { /** Rate of change starts out slowly and then accelerates. */ ACCELERATE, /** Rate of change starts and ends slowly but accelerates through the middle. */ ACCELERATE_DECELERATE, /** change starts backward then flings forward. */ ANTICIPATE, /** change starts backward, flings forward and overshoots the target value, then finally goes back to the final value. */ ANTICIPATE_OVERSHOOT, /** change bounces at the end. */ BOUNCE, /** Rate of change starts out quickly and and then decelerates. */ DECELERATE, /** Rate of change is constant. */ LINEAR, /** change flings forward and overshoots the last value then comes back. */ OVERSHOOT } /** Used to correctly call methods that use simple type parameters via reflection */ private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_MAP = buildPrimitiveTypeMap(); /** Inflates the mapping of data types to primitive types */ private static Map<Class<?>, Class<?>> buildPrimitiveTypeMap() { Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>(); map.put(Float.class, float.class); map.put(Double.class, double.class); map.put(Integer.class, int.class); map.put(Boolean.class, boolean.class); map.put(Long.class, long.class); map.put(Short.class, short.class); map.put(Byte.class, byte.class); return map; } /** * Keeps track of {@link LogFileObserver}s so they are not freed by garbage collection * before they are used */ private static List<LogFileObserver> fileObservers; /** * Provides access to the system and the layout */ private Context context; /** The current views that will be manipulated */ private List<View> views; /** The lowest level view registered with {@code this} droidQuery. */ private View rootView; /** * Optional data referenced by this droidQuery Object. Best practice is to make this a * {@link WeakReference} to avoid memory leaks. */ private Object data; /** Contains a mapping of {@code droidQuery} extensions. */ private static Map<String, Constructor<?>> extensions = new HashMap<String, Constructor<?>>(); /** Function to be called when this{@link #view} gains focus */ private Function onFocus; /** Function to be called when this {@link #view} no longer has focus. */ private Function offFocus; /** Function to be called when a key is pressed down when this {@link #view} has the focus. */ private Function keyDown; /** Function to be called when a key is pressed when this {@link #view} has the focus. */ private Function keyPress; /** Function to be called when a key is released when this {@link #view} has the focus. */ private Function keyUp; /** Function to be called when a swipe event is captured by this {@link #view}. */ private Function swipe; /** Function to be called when a swipe-up event is captured by this {@link #view}. */ private Function swipeUp; /** Function to be called when a swipe-down event is captured by this {@link #view}. */ private Function swipeDown; /** Function to be called when a swipe-left event is captured by this {@link #view}. */ private Function swipeLeft; /** Function to be called when a swipe-right event is captured by this {@link #view}. */ private Function swipeRight; /** Used to detect swipes on this {@link #view}. */ private SwipeDetector swiper; /** * Constructor. Accepts a {@code Context} Object. If the {@code context} is an {@link Activity}, * {@link #view} will be set to the content view. For example:<br> * <pre> * new $(context) * </pre> * @param context * @see #with(Context) */ public $(Context context) { this.context = context; this.views = new ArrayList<View>(); if (context instanceof Activity)//if an activity is used, and a contentView is set, use this as the view { View view = this.findViewById(android.R.id.content).getRootView(); if (view != null) { this.rootView = view; this.views.add(view); } else { //if no view is set, make sure not to get null pointers from view references. view = new View(context); this.rootView = view; this.views.add(view); } } else { View view = new View(context);//if view operations are attempted without the view set, this prevents null pointer exceptions this.rootView = view; this.views.add(view); } } /** * Constructor. Accepts a {@code View} Object.For example:<br> * <pre> * new $(myView) * </pre> * @param view * @see #with(View) */ public $(View view) { this.rootView = view; this.views = new ArrayList<View>(); this.views.add(view); this.context = view.getContext(); } /** * Constructor used to privately and safely allow the creation of droidQuery instances using with empty lists * @param context the current context * @param views the selection * @see #with(Context, List) */ private $(Context context, List<View> views) { if (views == null) views = new ArrayList<View>(); this.views = views; } /** * Constructor. Accepts a <em>List<View></em> Object. * @param view * @see #with(View) */ public $(List<View> views) { if (views == null) { throw new NullPointerException("Cannot create droidQuery Instance with null List."); } else if (views.isEmpty()) { throw new NullPointerException("Cannot create droidQuery Instance with empty List."); } this.rootView = views.get(0); this.context = this.rootView.getContext(); this.views = views; } /** * Constructor. Accepts a {@code View[]} Object or a Variable number of View objects (varargs). * @param views * @see #with(View) */ public $(View... views) { if (views == null) { throw new NullPointerException("Cannot create droidQuery Instance with null Array."); } else if (views.length == 0) { throw new NullPointerException("Cannot create droidQuery Instance with empty Array."); } this.rootView = views[0]; this.context = this.rootView.getContext(); this.views = Arrays.asList(views); } /** * Uses css selector to make a selection within the scope of the given parent view. * @param parent * @param selector */ public $(View parent, String selector) { this(parent); try { $ query = new CSSSelector().makeSelection(parent, TokenSequence.parse(selector)); this.rootView = query.rootView; this.context = query.context; this.views = query.views; } catch (Throwable t) { t.printStackTrace(); } } /** * Constructor that clones the selection and the data of another droidQuery instance. * @param droidQuery */ public $($ droidQuery) { this.rootView = droidQuery.rootView; this.views = new ArrayList<View>(droidQuery.views); this.context = droidQuery.context; this.data = droidQuery.data; } /** * Use css selectors to make selection * <pre> * new $(this, ".TextEdit"); * </pre> * @param context * @param resources */ public $(Context context, String selector) { this(context); try { $ query = new CSSSelector().makeSelection(this.rootView, TokenSequence.parse(selector)); this.rootView = query.rootView; this.context = query.context; this.views = query.views; } catch (Throwable t) { t.printStackTrace(); } } /** * Refreshes the listeners for focus changes */ private void setupFocusListener() { for (View view : views) { view.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus && onFocus != null) { onFocus.invoke($.with(v)); } else if (!hasFocus && offFocus != null) { offFocus.invoke($.with(v)); } } }); } } /** * Refreshes the listeners for key events */ private void setupKeyListener() { for (View view : views) { view.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { boolean retVal = false; switch (event.getKeyCode()) { case KeyEvent.ACTION_DOWN: { if (keyDown != null) { keyDown.invoke($.with(v), keyCode, event); retVal = true; } break; } case KeyEvent.ACTION_UP: { if (keyUp != null) { keyUp.invoke($.with(v), keyCode, event); retVal = true; } break; } } if (keyPress != null) { keyPress.invoke($.with(v), keyCode, event); retVal = true; } return retVal; } }); } } /** * Refreshes the listeners for swipe events */ private void setupSwipeListener() { swiper = new SwipeDetector(new SwipeListener() { @Override public void onUpSwipe(View v) { if (swipeUp != null) { swipeUp.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.UP); } } @Override public void onRightSwipe(View v) { if (swipeRight != null) { swipeRight.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.RIGHT); } } @Override public void onLeftSwipe(View v) { if (swipeLeft != null) { swipeLeft.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.LEFT); } } @Override public void onDownSwipe(View v) { if (swipeDown != null) { swipeDown.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.DOWN); } } @Override public void onStartSwipe(View v) { if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.START); } } @Override public void onStopSwipe(View v) { if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.STOP); } } }); for (View view : views) { view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent e) { return swiper.onTouch(v, e); } }); } } /** * Convenience method for initializing a droidQuery Object. For example: * <pre> * $.with(context) * </pre> * @param context * @return a new droidQuery instance */ public static $ with(Context context) { return new $(context); } /** * Convenience method for initializing a droidQuery Object. For example: * <pre> * $.with(myView) * </pre> * @param context * @return a new droidQuery instance */ public static $ with(View view) { return new $(view); } /** * Convenience method for initializing a droidQuery Object. For example: * <pre> * List<View> list = new ArrayList<View>(); * //manipute the list * $.with(list); * </pre> * @param views * @return a new droidQuery instance */ public static $ with(List<View> views) { return new $(views); } /** * Used to privately and safely allow the creation of droidQuery instances using with empty lists * @param context the current context * @param views the selection * @return a new instance of droidQuery with the selection set to the given views */ private static $ with(Context context, List<View> views) { return new $(context, views); } /** * Convenience method for initializing a droidQuery Object. For example: * <pre> * View[] views = new View[10]; * //manipute the array * $.with(views).attr("alpha", 0.5); * </pre> * or * <pre> * TextView tv = (TextView) findViewById(R.id.textview); * ImageView img = (ImageView) findViewById(R.id.img); * $.with(tv, img).attr("alpha", 0.5); * </pre> * @param views * @return a new droidQuery instance */ public static $ with(View... views) { return new $(views); } /** * Shortcut method for initializing a droidQuery Object and setting the view to manipulate. * For example:<pre> * $.with(this).id(R.id.main) * </pre> * @param context * @param id * @return a new droidQuery instance */ public static $ with(Context context, int id) { return $.with(context).id(id); } /** * * @param context * @param ids * @return */ public static $ with(Context context, int... ids) { return $.with(context).ids(ids); } /** * Selects the subviews specified by the given resource {@code ids}. * @param view * @param ids * @return */ public static $ with(View view, int... ids) { return $.with(view).ids(ids); } /** * Initiate new droidQuery with the given selector * @param context * @param selector * @return * @see $#$(Context, String) */ public static $ with(Context context, String selector) { return new $(context, selector); } /** * Initiate new droidQuery with the given selector * @param parent view scope * @param selector * @return * @see $#$(Context, String) */ public static $ with(View parent, String selector) { return new $(parent, selector); } /** * Create a copy of a droidQuery object that contains the same {@link #data()} and selection. * @param droidQuery * @return */ public static $ with($ droidQuery) { return new $(droidQuery); } /** * Finds a view that is identified by the given id * @param id * @return the found view, or {@code null} if it was not found. */ private View findViewById(int id) { //first check within the current selection for (View view : views) { if (view != null) { if (view.getId() == id) { return view; } } } View v = null; //if not found, check the current scope (rootView) if (rootView != null) v = rootView.findViewById(id); //if not found, check the Activity's scope if (v == null && context instanceof Activity) v = ((Activity) context).findViewById(id); return v; } /** Sets the set of views to the parents of all currently-selected views */ public $ parent() { List<View> _views = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewParent parent = view.getParent(); if (parent != null && !_views.contains(parent) && parent instanceof View) { _views.add((View) parent); } } this.views.clear(); this.views = _views; return this; } /** Sets the set of views to the children of the current set of views with the given child index */ public $ child(int index) { List<View> _views = new ArrayList<View>(); for (View view : views) { if (view instanceof ViewGroup) { View v = ((ViewGroup) view).getChildAt(index); if (v != null) _views.add(v); } } views.clear(); views = _views; return this; } /** * Animates the selected views using the JSON properties, the given duration, the easing function, * and with the onComplete callback * @param properties JSON String of an {@link AnimationOptions} Object * @param duration the duration of the animation, in milliseconds * @param easing the Easing function to use * @param complete the Function to invoke once the animation has completed for all views * @return this * @see Easing * @see #animate(Map, long, Easing, Function) * @see #animate(String, AnimationOptions) * @see #animate(Map, AnimationOptions) */ public $ animate(String properties, long duration, Easing easing, Function complete) { return animate(properties, AnimationOptions.create().duration(duration).easing(easing).complete(complete)); } /** * Animate the current views. Example: * <pre> * $.with(mView).animate("{ * left: 1000px, * top: 0%, * width: 50%, * alpha: 0.5 * }", * new AnimationOptions("{ duration: 3000, * easing: linear * }").complete(new Function() { * public void invoke($ droidQuery, Object... args) { * droidQuery.alert("Animation Complete!"); * } * }); * </pre> * @param properties to animate, in CSS representation * @param options the {@link AnimationOptions} for the animation * @return this */ public $ animate(String properties, AnimationOptions options) { try { JSONObject props = new JSONObject(properties); @SuppressWarnings("unchecked") Iterator<String> iterator = props.keys(); Map<String, Object> map = new HashMap<String, Object>(); while (iterator.hasNext()) { String key = iterator.next(); try { Object value = props.get(key); map.put(key, value); } catch (JSONException e) { Log.w("droidQuery", "Cannot handle CSS String. Some values may not be animated."); } } return animate(map, options); } catch (JSONException e) { Log.w("droidQuery", "Cannot handle CSS String. Unable to animate."); return this; } } /** * Animate the currently selected views. For example: * <pre> * $.with(myView).animate(new QuickMap(QuickEntry.qe("alpha", .8f), QuickEntry.qe("width", 50%)), 400, Easing.LINEAR, null); * </pre> * @param properties mapping of {@link AnimationOptions} attributes * @param duration the length of time for the animation to last * @param easing the Easing to use to interpolate the animation * @param complete the Function to call once the animation has completed or has been canceled. * @return this * @see QuickMap */ public $ animate(Map<String, Object> properties, long duration, Easing easing, final Function complete) { return animate(properties, AnimationOptions.create().duration(duration).easing(easing).complete(complete)); } /** * This reusable chunk of code can set up the given animation using the given animation options * @param options the options used to manipulate how the animation behaves * @return the container for placing views that will be animated using the given options */ private AnimatorSet animationWithOptions(final AnimationOptions options, List<Animator> animators) { AnimatorSet animation = new AnimatorSet(); animation.playTogether(animators); animation.setDuration(options.duration()); animation.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { if (options.fail() != null) options.fail().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } @Override public void onAnimationEnd(Animator animation) { if (options.success() != null) options.success().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); Interpolator interpolator = null; if (options.easing() == null) options.easing(Easing.LINEAR); final Easing easing = options.easing(); switch (easing) { case ACCELERATE: { interpolator = new AccelerateInterpolator(); break; } case ACCELERATE_DECELERATE: { interpolator = new AccelerateDecelerateInterpolator(); break; } case ANTICIPATE: { interpolator = new AnticipateInterpolator(); break; } case ANTICIPATE_OVERSHOOT: { interpolator = new AnticipateOvershootInterpolator(); break; } case BOUNCE: { interpolator = new BounceInterpolator(); break; } case DECELERATE: { interpolator = new DecelerateInterpolator(); break; } case OVERSHOOT: { interpolator = new OvershootInterpolator(); break; } //linear is default. case LINEAR: default: interpolator = new LinearInterpolator(); break; } //allow custom interpolator if (options.specialEasing() != null) interpolator = options.specialEasing(); animation.setInterpolator(interpolator); return animation; } /** * Interprets the CSS-style String and sets the value * @param view the view that will change. * @param key the name of the attribute * @param _value the end animation value * @return the computed value */ public Number getAnimationValue(View view, String key, String _value) { Number value = null; boolean negativeValue = false; if (_value.startsWith("-")) { negativeValue = true; _value = _value.substring(1); } String[] split = (_value).split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)"); if (negativeValue) split[0] = String.format(Locale.US, "-%s", split[0]); if (split.length == 1) { if (split[0].contains(".")) { value = Float.valueOf(split[0]); } else { value = Integer.valueOf(split[0]); } } else { if (split.length > 2) { Log.w("droidQuery", "parsererror for key " + key); return null; } if (split[1].equalsIgnoreCase("px")) { //this is the default. Just determine if float or int if (split[0].contains(".")) { value = Float.valueOf(split[0]); } else { value = Integer.valueOf(split[0]); } } else if (split[1].equalsIgnoreCase("dip") || split[1].equalsIgnoreCase("dp")) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, Float.parseFloat(split[0]), context.getResources().getDisplayMetrics()); if (split[0].contains(".")) { value = px; } else { value = (int) px; } } else if (split[1].equalsIgnoreCase("in")) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, Float.parseFloat(split[0]), context.getResources().getDisplayMetrics()); if (split[0].contains(".")) { value = px; } else { value = (int) px; } } else if (split[1].equalsIgnoreCase("mm")) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, Float.parseFloat(split[0]), context.getResources().getDisplayMetrics()); if (split[0].contains(".")) { value = px; } else { value = (int) px; } } else if (split[1].equalsIgnoreCase("pt")) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, Float.parseFloat(split[0]), context.getResources().getDisplayMetrics()); if (split[0].contains(".")) { value = px; } else { value = (int) px; } } else if (split[1].equalsIgnoreCase("sp")) { float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, Float.parseFloat(split[0]), context.getResources().getDisplayMetrics()); if (split[0].contains(".")) { value = px; } else { value = (int) px; } } else if (split[1].equals("%")) { ViewParent parent = view.getParent(); float pixels = 0; if (parent == null || !(parent instanceof View)) { pixels = context.getResources().getDisplayMetrics().widthPixels; //use best guess for width or height dpi if (split[0].equalsIgnoreCase("y") || split[0].equalsIgnoreCase("top") || split[0].equalsIgnoreCase("bottom")) { pixels = context.getResources().getDisplayMetrics().heightPixels; } } else { pixels = ((View) parent).getWidth(); if (split[0].equalsIgnoreCase("y") || split[0].equalsIgnoreCase("top") || split[0].equalsIgnoreCase("bottom")) { pixels = ((View) parent).getHeight(); } } float percent = 0; if (pixels != 0) percent = Float.valueOf(split[0]) / 100 * pixels; if (split[0].contains(".")) { value = percent; } else { value = (int) percent; } } else { Log.w("droidQuery", "invalid units for Object with key " + key); return null; } } return value; } /** * Animate multiple view properties at the same time. For example: * <pre> * $.with(myView).animate(new QuickMap(QuickEntry.qe("alpha", .8f), QuickEntry.qe("width", 50%)), new AnimationOptions()); * </pre> * @param properties mapping of property names and final values to animate * @param options the options for setting the duration, easing, etc of the animation * @return this */ public $ animate(Map<String, Object> properties, final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (Entry<String, Object> entry : properties.entrySet()) { final String key = entry.getKey(); //Java sometimes will interpret these Strings as Numbers, so some trickery is needed below Object value = entry.getValue(); for (final View view : this.views) { ValueAnimator anim = null; if (value instanceof String) value = getAnimationValue(view, key, (String) value); if (value != null) { final ViewGroup.LayoutParams params = view.getLayoutParams(); try { final Field field = params.getClass().getField(key); if (field != null) { if (value instanceof Integer || value.getClass() == int.class) anim = ValueAnimator.ofInt((Integer) field.get(params), (Integer) value); else if (value instanceof Float || value.getClass() == float.class) anim = ValueAnimator.ofFloat((Float) field.get(params), (Float) value); else if (value instanceof Long || value.getClass() == long.class) anim = ValueAnimator.ofFloat((Float) field.get(params), new Float((Long) value)); else if (value instanceof Double || value.getClass() == double.class) anim = ValueAnimator.ofFloat((Float) field.get(params), new Float((Double) value)); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { try { field.set(params, animation.getAnimatedValue()); view.requestLayout(); } catch (Throwable t) { if (options.debug()) t.printStackTrace(); } if (options.progress() != null) { options.progress().invoke($.with(view), key, animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } } }); } } catch (Throwable t) { if (options.debug()) Log.w("$", String.format(Locale.US, "%s is not a LayoutParams attribute.", key)); } if (anim == null) { if (value instanceof Integer || value.getClass() == int.class) anim = ObjectAnimator.ofInt(view, key, (Integer) value); else if (value instanceof Float || value.getClass() == float.class) anim = ObjectAnimator.ofFloat(view, key, (Float) value); else if (value instanceof Long || value.getClass() == long.class) anim = ObjectAnimator.ofFloat(view, key, new Float((Long) value)); else if (value instanceof Double || value.getClass() == double.class) anim = ObjectAnimator.ofFloat(view, key, new Float((Double) value)); if (options.progress() != null) { anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { options.progress().invoke($.with(view), key, animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } }); } } anim.setRepeatCount(options.repeatCount()); if (options.reverse()) anim.setRepeatMode(ValueAnimator.REVERSE); animations.add(anim); } } } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); return this; } /** * Shortcut method for animating the alpha attribute of this {@link #view} to 1.0. * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeIn(AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(1.0f))), options); } /** * Shortcut method for animating the alpha attribute of the selected views to 1.0. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeIn(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); anim.setDuration(duration); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1.0f); if (builder == null) { builder = anim.play(animator); } else { builder.with(animator); } } anim.start(); } /** * Shortcut method for animating the alpha attribute of the selected views to 0.0. * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeOut(AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(0.0f))), options); } /** * Shortcut method for animating the alpha attribute of this {@link #view} to 0.0. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeOut(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); anim.setDuration(duration); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0.0f); if (builder == null) { builder = anim.play(animator); } else { builder.with(animator); } } anim.start(); } /** * Shortcut method for animating the alpha attribute of the selected views to the given value. * @param opacity the alpha value at the end of the animation * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeTo(float opacity, AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(opacity))), options); } /** * Shortcut method for animating the alpha attribute of this {@link #view} to the given value. * @param duration the length of time the animation should last * @param opacity the alpha value at the end of the animation * @param complete the function to call when the animation has completed */ public void fadeTo(long duration, float opacity, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); anim.setDuration(duration); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", opacity); if (builder == null) { builder = anim.play(animator); } else { builder.with(animator); } } anim.start(); } /** * For each selected view, if its alpha is less than 0.5, it will fade in. Otherwise, it will * fade out. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeToggle(long duration, final Function complete) { List<View> zeros = new ArrayList<View>(); List<View> ones = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); if (ViewHelper.getAlpha(view) < 0.5) zeros.add(view); else ones.add(view); } $.with(context, zeros).fadeIn(duration, complete); $.with(context, ones).fadeOut(duration, complete); } /** * If this {@link #view} has an alpha of less than 0.5, it will fade in. Otherwise, it will * fade out. * @param options use to modify the behavior of the animation */ public void fadeToggle(AnimationOptions options) { List<View> zeros = new ArrayList<View>(); List<View> ones = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); if (ViewHelper.getAlpha(view) < 0.5) zeros.add(view); else ones.add(view); } $.with(context, zeros).fadeIn(options); $.with(context, ones).fadeOut(options); } /** * Animates the selected views out of their parent views by sliding it down, past its bottom * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideDown(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewParent parent = view.getParent(); float y = 0; if (parent != null && parent instanceof View) { y = ((View) parent).getHeight(); } else { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); y = display.getHeight(); } ObjectAnimator animator = ObjectAnimator.ofFloat(view, "y", y); if (builder == null) builder = anim.play(animator); else builder.with(animator); } anim.setDuration(duration); anim.start(); } /** * Animates the selected views out of its parent by sliding it down, past its bottom * @param options use to modify the behavior of the animation */ public void slideDown(final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (final View view : this.views) { ViewParent parent = view.getParent(); float y = 0; if (parent != null && parent instanceof View) { y = ((View) parent).getHeight(); } else { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); y = display.getHeight(); } ObjectAnimator anim = ObjectAnimator.ofFloat(view, "y", new Float(y)); if (options.progress() != null) { anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { options.progress().invoke($.with(view), "y", animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } }); } animations.add(anim); } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); } /** * Animates the selected views out of its parent by sliding it up, past its top * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideUp(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "y", 0); if (builder == null) builder = anim.play(animator); else builder.with(animator); } anim.setDuration(duration); anim.start(); } /** * Animates the selected views out of its parent by sliding it up, past its top * @param options use to modify the behavior of the animation */ public void slideUp(final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (final View view : this.views) { ObjectAnimator anim = ObjectAnimator.ofFloat(view, "y", new Float(0)); if (options.progress() != null) { anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { options.progress().invoke($.with(view), "y", animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } }); } animations.add(anim); } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); } /** * Animates the selected views out of its parent by sliding it right, past its edge * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideRight(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewParent parent = view.getParent(); float x = 0; if (parent != null && parent instanceof View) { x = ((View) parent).getWidth(); } else { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); x = display.getHeight(); } ObjectAnimator animator = ObjectAnimator.ofFloat(view, "x", x); if (builder == null) builder = anim.play(animator); else builder.with(animator); } anim.setDuration(duration); anim.start(); } /** * Animates the selected views out of its parent by sliding it right, past its edge * @param options use to modify the behavior of the animation */ public void slideRight(final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (final View view : this.views) { ViewParent parent = view.getParent(); float x = 0; if (parent != null && parent instanceof View) { x = ((View) parent).getWidth(); } else { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); x = display.getHeight(); } ObjectAnimator anim = ObjectAnimator.ofFloat(view, "x", x); if (options.progress() != null) { anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { options.progress().invoke($.with(view), "x", animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } }); } animations.add(anim); } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); } /** * Animates the selected views out of its parent by sliding it left, past its edge * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideLeft(long duration, final Function complete) { AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimatorListener() { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (complete != null) complete.invoke($.this); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } }); AnimatorSet.Builder builder = null; for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "x", 0); if (builder == null) builder = anim.play(animator); else builder.with(animator); } anim.setDuration(duration); anim.start(); } /** * Animates the selected views out of its parent by sliding it left, past its edge * @param options use to modify the behavior of the animation */ public void slideLeft(final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (final View view : this.views) { ObjectAnimator anim = ObjectAnimator.ofFloat(view, "x", 0); if (options.progress() != null) { anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { options.progress().invoke($.with(view), "x", animation.getAnimatedValue(), animation.getDuration() - animation.getCurrentPlayTime()); } }); } animations.add(anim); } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); } /** * Gets the value for the given attribute of the first view in the current selection. * This is done using reflection, and as such * expects a <em>get-</em> or <em>is-</em> prefixed method name for the view. * @param s the name of the attribute to retrieve * @return the value of the given attribute name on the first view in the current selection */ public Object attr(String s) { try { Method m = view(0).getClass().getMethod("get" + capitalize(s)); return m.invoke(view(0)); } catch (Throwable t) { try { Method m = view(0).getClass().getMethod("is" + capitalize(s)); return m.invoke(view(0)); } catch (Throwable t2) { //try using NineOldAndroids try { Method m = ViewHelper.class.getMethod("get" + capitalize(s), new Class<?>[] { View.class }); return m.invoke(null, view(0)); } catch (Throwable t3) { Log.w("droidQuery", view(0).getClass().getSimpleName() + "has no getter method for the variable " + s + "."); } return null; } } } /** * Sets the value of the given attribute on each view in the current selection. This is done * using reflection, and as such a <em>set-</em>prefixed method name for each view. * @param s the name of the attribute to set * @param o the value to set to the given attribute * @return this * TODO add additional support for common CSS syntax */ public $ attr(String s, Object o) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); try { Class<?> objClass = o.getClass(); Class<?> simpleClass = PRIMITIVE_TYPE_MAP.get(objClass); if (simpleClass != null) { objClass = simpleClass; } try { Method m = view.getClass().getMethod("set" + capitalize(s), new Class<?>[] { objClass }); m.invoke(view, o); } catch (Throwable t) { //try using NineOldAndroids try { Method m = ViewHelper.class.getMethod("set" + capitalize(s), new Class<?>[] { View.class, objClass }); m.invoke(null, view(0), o); } catch (Throwable t2) { Log.w("droidQuery", view.getClass().getSimpleName() + ".set" + capitalize(s) + "(" + o.getClass().getSimpleName() + ") is not a method!"); } } } catch (Throwable t) { Log.w("droidQuery", view.getClass().getSimpleName() + ".set" + capitalize(s) + "(" + o.getClass().getSimpleName() + ") is not a method!"); } } return this; } /** * @return the view at the given index of the current selection */ public View view(int index) { return this.views.get(index); } /** * @return the current context */ public Context context() { return this.context; } /** * @return the current data. */ public Object data() { return this.data; } /** * Sets the data associated with this droidQuery * @param data the data to set * @return this */ public $ data(Object data) { this.data = data; return this; } /** * Adds a subview to the first view in the selection * @param v the subview to add * @return this */ public $ add(View v) { if (v == null || v.getParent() != null) { Log.w("droidQuery", "Cannot add View"); return this; } if (view(0) instanceof ViewGroup) { ((ViewGroup) view(0)).addView(v); } return this; } /** * Adds a subview to the first view in the current selection * @param v the id of the subview to add * @return this */ public $ add(int id) { View v = findViewById(id); if (v == null || v.getParent() != null) { Log.w("droidQuery", "Cannot add View"); return this; } if (view(0) instanceof ViewGroup) { ((ViewGroup) view(0)).addView(v); } return this; } /* TODO include these CSS methods once css parsing and handling is complete! */ // // /** // * Get a key-value mapping of the css values for the given property names, for the first selected view. // * @param propertyNames // * @return // */ // public Map<String, Object> css(String[] propertyNames) // { // //TODO // } // // /** // * Set a CSS property for the set of matched elements // * @param propertyName // * @param value // * @return // */ // public $ css(String propertyName, Object value) // { // //TODO // } // // /** // * Handle getting/setting each CSS property // * @param propertyName the name of the property // * @param function includes a droidQuery selected with each element, and args that include the index and the property's value // * @return // */ // public $ css(String propertyName, Function function) // { // //TODO // } // // /** // * Set the css for the selected views // * @param properties json object as string // */ // public $ css(String properties) // { // try { // StyleSheet.fromString(properties).applyRules(this); // } catch (Exception e) { // Log.w("droidQuery", "Could not load css", e); // } // return this; // } // // /** // * Applies the CSS-style rules to the selection. // * @param css // * @return // */ // public $ css(JSONObject properties) // { // return css(properties.toString()); // } /** * Removes a subview from the first view in the current selection * @param v the subview to remove * @return this */ public $ remove(View v) { if (view(0) instanceof ViewGroup) { ((ViewGroup) view(0)).removeView(v); } return null; } /** * Removes a subview from the first view in the current selection * @param v the id of the subview to remove * @return this */ public $ remove(int id) { View v = findViewById(id); if (v == null) { Log.w("droidQuery", "Cannot remove View"); return this; } if (view(0) instanceof ViewGroup) { ((ViewGroup) view(0)).removeView(v); } return this; } /** * Sets the visibility of the current selection to {@link View#VISIBLE} * @return this */ public $ hide() { for (View view : views) { view.setVisibility(View.VISIBLE); } return this; } /** * Sets the visibility of this {@link #view} to {@link View#INVISIBLE} * @return this */ public $ show() { for (View view : views) { view.setVisibility(View.INVISIBLE); } return this; } ///Event Handler Attachment /** * Binds the views in the current selection to the event. For example: * <pre> * $.with(myView).bind("click", "Hello World!", new Function() { * public void invoke($ droidQuery, Object... args) { * Object data = args[0]; * droidQuery.alert((String) data); * } * }); * </pre> * @note that for events with multiple words, all words except the first are required to be * capitalized. For example, to bind to a long-click event, both of the following are acceptable: * <pre> * $.with(myView).bind("longClick", "Hello World!", new Function() { * public void invoke($ droidQuery, Object... args) { * Object data = args[0]; * droidQuery.alert((String) data); * } * }); * * $.with(myView).bind("LongClick", "Hello World!", new Function() { * public void invoke($ droidQuery, Object... args) { * Object data = args[0]; * droidQuery.alert((String) data); * } * }); * </pre> * However, this will fail: * <pre> * $.with(myView).bind("longclick", "Hello World!", new Function() { * public void invoke($ droidQuery, Object... args) { * Object data = args[0]; * droidQuery.alert((String) data); * } * }); * </pre> * @param eventType should be the verb in OnVerbListener * @param data an Object passed to {@code handler} when the event is triggered. * @param handler receives two arguments: a droidQuery with the affected view selected, and the {@code data} parameter * @return the current instance of {@code droidQuery} * @see #on(String, Function) * @see #one(String, Function) * @see #unbind(String) */ public $ bind(String eventType, Object data, Function handler) { String method = String.format(Locale.US, "setOn%sListener", capitalize(eventType)); String listener = String.format(Locale.US, "On%sListener", capitalize(eventType)); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); Class<?>[] classes = view.getClass().getClasses(); try { //dynamically create instance of the listener interface Class<?> eventInterface = null; for (Class<?> clazz : classes) { if (clazz.getSimpleName().equalsIgnoreCase(listener)) { eventInterface = clazz; break; } } Method setEventListener = view.getClass().getMethod(method, new Class<?>[] { eventInterface }); EventHandlerCreator proxy = new EventHandlerCreator($.with(view), handler, data); Object eventHandler = Proxy.newProxyInstance(eventInterface.getClassLoader(), new Class<?>[] { eventInterface }, proxy); setEventListener.invoke(view, eventInterface.cast(eventHandler)); } catch (Throwable t) { Log.w("droidQuery", String.format(Locale.US, "Could not bind to event %s.\n%s", eventType, t.getMessage())); } } return this; } /** * Binds the views in the current selection to the event. For example: * <pre> * $.with(myView).on("click", new Function() { * public void invoke($ droidQuery, Object... args) { * droidQuery.alert("View Clicked!"); * } * }); * </pre> * @note that for events with multiple words, all words except the first are required to be * capitalized. For example, to bind to a long-click event, both of the following are acceptable: * <pre> * $.with(myView).on("longClick", new Function() { * public void invoke($droidQuery, Object... args) { * droidQuery.alert("View LongClicked!"); * } * }); * * $.with(myView).on("LongClick", new Function() { * public void invoke($ droidQuery, Object... args) { * droidQuery.alert("View LongClicked!"); * } * }); * </pre> * However, this will fail: * <pre> * $.with(myView).on("longclick", new Function() { * public void invoke($ droidQuery, Object... args) { * droidQuery.alert("View LongClicked!"); * } * }); * </pre> * @param event should be the verb in OnVerbListener * @param handler receives one argument: a droidQuery with the affected view selected * @return the current instance of {@code droidQuery} * @see #bind(String, Object, Function) * @see #one(String, Function) * @see #unbind(String) */ public $ on(String event, Function handler) { String method = String.format(Locale.US, "setOn%sListener", capitalize(event)); String listener = String.format(Locale.US, "On%sListener", capitalize(event)); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); Class<?>[] classes = view.getClass().getClasses(); try { Class<?> eventInterface = null; for (Class<?> clazz : classes) { if (clazz.getSimpleName().equalsIgnoreCase(listener)) { eventInterface = clazz; break; } } Method setEventListener = view.getClass().getMethod(method, new Class<?>[] { eventInterface }); EventHandlerCreator proxy = new EventHandlerCreator($.with(view), handler); Object eventHandler = Proxy.newProxyInstance(eventInterface.getClassLoader(), new Class<?>[] { eventInterface }, proxy); setEventListener.invoke(view, eventInterface.cast(eventHandler)); } catch (Throwable t) { Log.w("droidQuery", String.format(Locale.US, "Could not bind to event %s.", event)); } } return this; } /** * Like {@link #on(String, Function)}, but the function will only run once for the event. Future * events will not trigger the given function * @param event the name of the event * @param handler the function to invoke the first time the event occurs * @return this */ public $ one(final String event, final Function handler) { Function function = new Function() { @Override public void invoke($ droidQuery, Object... params) { handler.invoke(droidQuery); droidQuery.unbind(event); } }; return on(event, function); } //these listenTo methods expose the EventCenter, and provide a behavior similar to that provided //by backbone.js /** * Listen for all events triggered using the {@link #notify(String)} or {@link #notify(String, Map)} * method * @param event the name of the event * @param callback the function to call when the event is triggered. * @see #listenToOnce(String, Function) */ public static void listenTo(String event, Function callback) { EventCenter.bind(event, callback, null); } /** * Listen for the next event triggered using the {@link #notify(String)} or * {@link #notify(String, Map)} method * @param event the name of the event * @param callback the function to call when the event is triggered. * @see #listenTo(String, Function) */ public static void listenToOnce(final String event, final Function callback) { EventCenter.bind(event, new Function() { @Override public void invoke($ droidQuery, Object... params) { callback.invoke(droidQuery, params); EventCenter.unbind(event, this, null); } }, null); } /** * Stop listening for events triggered using the {@link #notify(String)} and * {@link #notify(String, Map)} methods * @param event the name of the event * @param callback the function to no longer call when the event is triggered. * @see #listenTo(String, Function) */ public static void stopListening(String event, Function callback) { EventCenter.unbind(event, callback, null); } /** * Trigger a notification for functions registered to the given event String * @param event the event string to which registered listeners will respond * @see #listenTo(String, Function) * @see #listenToOnce(String, Function) */ public $ notify(String event) { EventCenter.trigger(this, event, null, null); return this; } /** * Trigger a notification for functions registered to the given event String * @param event the event string to which registered listeners will respond * @param data Object passed to the notified functions * @see #listenTo(String, Function) * @see #listenToOnce(String, Function) */ public $ notify(String event, Map<String, Object> data) { EventCenter.trigger(this, event, data, null); return this; } /** * Registers change listeners for TextViews, EditTexts, and CompoundButtons. For all other * view types, this will trigger a function when the view's layout has been changed. * @param function the Function to call when the change event occurs. This will receive a * droidQuery instance for the changed view * @return this */ public $ change(final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); final int index = i; if (view instanceof TextView) { ((TextView) view).addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable arg0) { function.invoke($.with($.this.views.get(index))); } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }); } else if (view instanceof EditText) {//this is overkill, but what the hey ((EditText) view).addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable arg0) { function.invoke($.with($.this.views.get(index))); } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }); } else if (view instanceof CompoundButton) { ((CompoundButton) view).setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { function.invoke($.with($.this.views.get(index))); } }); } else if (android.os.Build.VERSION.SDK_INT >= 11) { //default to size for API 11+ try { Class<?> eventInterface = Class.forName("android.view.View.OnLayoutChangeListener"); Method addOnLayoutChangeListener = view.getClass().getMethod("addOnLayoutChangeListener", new Class<?>[] { eventInterface }); InvocationHandler proxy = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { function.invoke($.with($.this.views.get(index))); return null; } }; Object eventHandler = Proxy.newProxyInstance(eventInterface.getClassLoader(), new Class<?>[] { eventInterface }, proxy); addOnLayoutChangeListener.invoke(view, eventInterface.cast(eventHandler)); } catch (Throwable t) { //unknown error } } } return this; } /** * Get the value associated with the first view in the current selection. If the view is a * TextView, this method returns the CharSequence text. If it is a Button, the boolean checked * state is returned. If it is an ImageView, the Drawable is returned. * @return the value of this view, or <em>null</em> if not applicable. */ public Object val() { if (view(0) instanceof TextView) { return ((TextView) view(0)).getText(); } else if (view(0) instanceof CompoundButton) { return ((CompoundButton) view(0)).isChecked(); } else if (view(0) instanceof ImageView) { return ((ImageView) view(0)).getDrawable(); } return null; } /** * Set the value associated with the views in the current selection. If the view is a TextView, * this method sets the CharSequence text. If it is a Button, the boolean checked state is set. * If it is an ImageView, the Drawable or Bitmap is set. All other view types are ignored. * @return this */ public $ val(Object object) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); if (view instanceof TextView && object instanceof CharSequence) { ((TextView) view).setText((CharSequence) object); } else if (view instanceof CompoundButton && object instanceof Boolean) { ((CompoundButton) view).setChecked((Boolean) object); } else if (view instanceof ImageView) { if (object instanceof Bitmap) { ((ImageView) view).setImageBitmap((Bitmap) object); } else if (object instanceof Drawable) { ((ImageView) view).setImageDrawable((Drawable) object); } } } return this; } /** * Triggers a click event on the views in the current selection * @return this */ public $ click() { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.performClick(); } return this; } /** * Invokes the given Function every time each view in the current selection is clicked. The only * parameter passed to the given function is a droidQuery instance containing the clicked view * @param function the function to call when this view is clicked * @return this */ public $ click(final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { function.invoke($.with(v)); } }); } return this; } /** * Invokes the given Function for click events on each view in the current selection. * The function will receive two arguments: * <ol> * <li>a droidQuery containing the clicked view * <li>{@code eventData} * </ol> * @param eventData the second argument to pass to the {@code function} * @param function the function to invoke * @return */ public $ click(final Object eventData, final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { function.invoke($.with(v), eventData); } }); } return this; } /** * Triggers a long-click event on this each view in the current selection * @return this */ public $ longclick() { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.performLongClick(); } return this; } /** * Invokes the given Function every time each view in the current selection is long-clicked. * The only parameter passed to the given function a droidQuery instance with the long-clicked view. * @param function the function to call when this view is long-clicked * @return this */ public $ longclick(final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { function.invoke($.with(v)); return true; } }); } return this; } /** * Invokes the given Function for long-click events on the views in the current selection. * The function will receive two arguments: * <ol> * <li>a droidQuery containing the long-clicked view * <li>{@code eventData} * </ol> * @param eventData the second argument to pass to the {@code function} * @param function the function to invoke * @return */ public $ longclick(final Object eventData, final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { function.invoke($.with(v), eventData); return true; } }); } return this; } /** * Handles swipe events. This will override any onTouchListener added. * @param function will receive this droidQuery and a {@link SwipeDetector.Direction} corresponding * to the direction of the swipe. * @return this */ public $ swipe(Function function) { swipe = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Up. This will cause any function set by * {@link #swipe(Function)} to not get called for up events. * @param function the function to invoke * @return this */ public $ swipeUp(Function function) { swipeUp = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Left. This will cause any function set by * {@link #swipe(Function)} to not get called for left events. * @param function the function to invoke * @return this */ public $ swipeLeft(Function function) { swipeLeft = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Down. This will cause any function set by * {@link #swipe(Function)} to not get called for down events. * @param function the function to invoke * @return this */ public $ swipeDown(Function function) { swipeDown = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Right. This will cause any function set by * {@link #swipe(Function)} to not get called for right events. * @param function the function to invoke * @return this */ public $ swipeRight(Function function) { swipeRight = function; setupSwipeListener(); return this; } /** * Triggers a swipe-up event on the current selection * @return this */ public $ swipeUp() { if (swiper != null) swiper.performSwipeUp(); return this; } /** * Triggers a swipe-down event on the current selection * @return this */ public $ swipeDown() { if (swiper != null) swiper.performSwipeDown(); return this; } /** * Triggers a swipe-left event on the current selection * @return this */ public $ swipeLeft() { if (swiper != null) swiper.performSwipeLeft(); return this; } /** * Triggers a swipe-right event on the current selection * @return this */ public $ swipeRight() { if (swiper != null) swiper.performSwipeRight(); return this; } /** * Sets the function to call when a view in the current selection has gained focus. This function * will receive an instance of droidQuery for this view as its only parameter * @param function the function to invoke * @return this */ public $ focus(Function function) { onFocus = function; setupFocusListener();//fixes any changes to the onfocuschanged listener return this; } /** * Gives focus to the first focusable view in the current selection. * @return this */ public $ focus() { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); if (view.requestFocus()) { break; } } return this; } /** * Sets the function to call when this {@link #view} loses focus. * @param function the function to invoke. Will receive a droidQuery instance containing * the view as its only parameter * @return this */ public $ focusout(Function function) { offFocus = function; setupFocusListener(); return this; } /** * Removes focus from all views in the current selection. * @return this */ public $ focusout() { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.clearFocus(); } return this; } /** * Set the function to call when a key-down event has been detected on this view. * @param function the Function to invoke. Receives a droidQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keydown(Function function) { keyDown = function; setupKeyListener(); return this; } /** * Set the function to call when a key-press event has been detected on this view. * @param function the Function to invoke. Receives a droidQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keypress(Function function) { keyPress = function; setupKeyListener(); return this; } /** * Set the function to call when a key-up event has been detected on this view. * @param function the Function to invoke. Receives a droidQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keyup(Function function) { keyUp = function; setupKeyListener(); return this; } /** * For each subclass on an {@link AdapterView} in the current selection, {@code select(Function)} * will register an {@link AdapterView.OnItemSelectedListener OnItemSelectedListener} to invoke * the given function. * @param function function to invoke. Receives a droidQuery with the {@code AdapterView} selected, * and the view position (int) as the only varargs parameter. * @return this */ public $ select(final Function function) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); if (view instanceof AdapterView) { ((AdapterView<?>) view).setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { function.invoke($.with(view), position); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } } return this; } /** * For each subclass of an {@link AdapterView} in the current selection, this will set the item * selection to the given position. * @param index the index of the child view to select * @return this */ public $ select(int index) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); try { Method m = view.getClass().getMethod("setSelection", new Class<?>[] { Integer.class }); m.invoke(view, index); } catch (Throwable t) { //not available } } return this; } /** * Remove a previously-attached event handler from the views in the current selection. * This can remove events registered * by {@link #bind(String, Object, Function)}, {@link #on(String, Function)}, {@link #click()}, * etc.- or directly on the view. * @param eventType the name of the event to unbind */ public void unbind(String eventType) { String method = String.format(Locale.US, "setOn%sListener", capitalize(eventType)); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); String listener = String.format(Locale.US, "%s.On%sListener", view.getClass().getName(), capitalize(eventType)); try { //dynamically create instance of the listener interface Class<?> eventInterface = Class.forName(listener); Method setEventListener = view.getClass().getMethod(method, new Class<?>[] { eventInterface }); EventHandlerCreator proxy = new EventHandlerCreator($.with(view), $.noop()); Object eventHandler = Proxy.newProxyInstance(eventInterface.getClassLoader(), new Class<?>[] { eventInterface }, proxy); setEventListener.invoke(view, eventInterface.cast(eventHandler)); } catch (Throwable t) { Log.w("droidQuery", String.format(Locale.US, "Could not unbind from event %s.", eventType)); } } } /////Miscellaneous /** * Invokes the given function for each view in the current selection. Function receives a * droidQuery instance containing the single view, and an integer of the current index * @param function the function to invoke * @return this */ public $ each(Function function) { for (int i = 0; i < views.size(); i++) { function.invoke($.with(views.get(i)), i); } return this; } /** * If the first view of the current selection is a subclass of {@link AdapterView}, this will loop through all the * adapter data and invoke the given function, passing the varargs: * <ol> * <li>the item from the adapter * <li>the index * </ol> * Otherwise, if the first view in the current selection is a subclass of {@link ViewGroup}, {@code each} will * loop through all the child views, and wrap each one in a droidQuery object. The invoked * function will receive it, and an int for the index of the selected child view. * @param function Function the function to invoke * @return this */ public $ children(Function function) { if (view(0) instanceof AdapterView) { AdapterView<?> group = (AdapterView<?>) view(0); for (int i = 0; i < (group).getCount(); i++) { function.invoke($.this, group.getItemAtPosition(i), i); } } else if (view(0) instanceof ViewGroup) { ViewGroup group = (ViewGroup) view(0); for (int i = 0; i < group.getChildCount(); i++) { function.invoke($.with(group.getChildAt(i)), i); } } return this; } /** * Loops through all the sibling views of the first view in the current selection, and wraps * each in a droidQuery object. When invoked, the given function will receive two parameters: * <ol> * <li>the droidQuery for the view * <li>the child index of the sibling * </ol> * @param function receives the droidQuery for the view, and the index for arg1 */ public $ siblings(Function function) { ViewParent parent = view(0).getParent(); if (parent != null && parent instanceof ViewGroup) { ViewGroup group = (ViewGroup) parent; for (int i = 0; i < group.getChildCount(); i++) { function.invoke($.with(group.getChildAt(i)), i); } } return this; } /** * Gets all the views in the current selection after the given start index * @param start the starting position of the views to pass to the new instance of droidQuery. * @return a droidQuery object containing the views from {@code start} to the end of the list. */ public $ slice(int start) { return $.with(context, this.views.subList(start, this.views.size())); } /** * Gets all the views in the current selection after the given start index and before the given * end index * @param start the starting position of the views to pass to the new instance of droidQuery. * @return a droidQuery object containing the views from {@code start} to {@code end}. */ public $ slice(int start, int end) { return $.with(context, this.views.subList(start, end)); } /** @return the number of views that are currently selected */ public int length() { return this.views.size(); } /** @return the number of views that are currently selected */ public int size() { return length(); } /** * Checks to see if the first view in the current selection is a subclass of the given class name * @param className the name of the superclass to check * @return {@code true} if the view is a subclass of the given class name. * Otherwise, {@code false}. */ public boolean is(String className) { try { Class<?> clazz = Class.forName(className); if (clazz.isInstance(view(0))) return true; return false; } catch (Throwable t) { return false; } } /** * Checks to see if the given Object is a subclass of the given class name * @param obj the Object to check * @param className the name of the superclass to check * @return {@code true} if the view is a subclass of the given class name. * Otherwise, {@code false}. */ public static boolean is(Object obj, String className) { try { Class<?> clazz = Class.forName(className); if (clazz.isInstance(obj)) return true; return false; } catch (Throwable t) { return false; } } /** * Removes each view in the current selection from the layout */ public void remove() { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewParent parent = view.getParent(); if (parent != null && parent instanceof ViewGroup) { ((ViewGroup) parent).removeView(view); } } } /////Selectors /** * Recursively selects all subviews of the given view * @param v the parent view of all the suclasses to select * @return a list of all views that are subviews in the * view hierarchy with the given view as the root */ private List<View> recursivelySelectAllSubViews(View v) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectAllSubViews(((ViewGroup) v).getChildAt(i))); } } list.add(v); return list; } /** * Recursively selects all subviews of the given view that are subclasses of the given Object type * @param v the parent view of all the suclasses to select * @return a list of all views that are subviews in the * view hierarchy with the given view as the root */ private List<View> recursivelySelectByType(View v, Class<?> clazz) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectByType(((ViewGroup) v).getChildAt(i), clazz)); } } if (clazz.isInstance(v)) list.add(v); return list; } /** * Select all subviews of the currently-selected views * @return a droidQuery Object with all the views */ public $ selectAll() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectAllSubViews(view)); } return $.with(context, subviews); } /** * Select all subviews of the currently-selected views that are subclasses of the given {@code className}. * @param className * @return all the selected views in a droidQuery wrapper */ public $ selectByType(String className) { Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Throwable t) { try { clazz = Class.forName(String.format(Locale.US, "android.widget.%s", className)); } catch (Throwable t2) { try { clazz = Class.forName(String.format(Locale.US, "android.webkit.%s", className)); } catch (Throwable t3) { return null; } } } return selectByType(clazz); } /** * Select all subviews of the currently-selected views that are subclasses of the given class. * @param clazz class * @return all the selected views in a droidQuery wrapper */ public $ selectByType(Class<?> clazz) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectByType(view, clazz)); } return $.with(context, subviews); } /** * Selects the child views of the first view in the current selection * @return a droidQuery Objects containing the child views. If the view is a subclass of * {@link AdapterView}, the {@link #data() data} of the droidQuery will be set to an Object[] * of adapter items. */ public $ selectChildren() { List<View> list = new ArrayList<View>(); if (view(0) instanceof AdapterView) { AdapterView<?> adapter = (AdapterView<?>) view(0); Object[] data = new Object[adapter.getCount()]; for (int i = 0; i < adapter.getCount(); i++) { data[i] = adapter.getItemAtPosition(i); } return $.with(view(0)).data(data); } else if (view(0) instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view(0)).getChildCount(); i++) { list.add(((ViewGroup) view(0)).getChildAt(i)); } } return $.with(context, list); } /** * Selects all subviews of the given view that do not contain subviews * @param v the view whose subviews will be retrieved * @return a list empty views */ private List<View> recursivelySelectEmpties(View v) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup && ((ViewGroup) v).getChildCount() > 0) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectEmpties(((ViewGroup) v).getChildAt(i))); } } else if (!(v instanceof AdapterView && ((AdapterView<?>) v).getCount() > 0)) { list.add(v); } return list; } /** * Select all non-ViewGroups, or ViewGroups with no children, that lay within the view * hierarchy of the current selection * @return a droidQuery object containing the selection */ public $ selectEmpties() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectEmpties(view)); } return $.with(context, subviews); } /** * Searches the view hierarchy rooted at the given view in order to find the currently * focused view * @param view the view to search within * @return the selected view, or null if no view in the given hierarchy was focused. */ private View recursivelyFindFocusedSubView(View view) { if (view.isFocused()) return view; else if (view instanceof ViewGroup) { View v = null; for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { v = recursivelyFindFocusedSubView(((ViewGroup) view).getChildAt(i)); if (v != null) return v; } return null; } else return null; } /** * Selects the currently-focused view. * @return a droidQuery Object created with the currently-focused View, if there is one */ public $ selectFocused() { View focused = recursivelyFindFocusedSubView(rootView); if (focused != null) return $.with(focused); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); focused = recursivelyFindFocusedSubView(view); if (focused != null) return $.with(focused); } return $.with(view(0).getContext()); } /** * Selects the currently-selected views. * @return a droidQuery Object created with the currently-selected View, if there is one */ public $ selectSelected() { List<View> selected = recursivelyFindSelectedSubView(rootView); if (selected != null) return $.with(selected); selected = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); selected.addAll(recursivelyFindSelectedSubView(view)); } return $.with(view(0).getContext()); } /** * Searches the view hierarchy rooted at the given view in order to find the currently * selected views * @param view the view to search within * @return the selected views, or null if no view in the given hierarchy was selected. */ private static List<View> recursivelyFindSelectedSubView(View view) { List<View> views = new ArrayList<View>(); if (view.isSelected()) views.add(view); else if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { views.addAll(recursivelyFindSelectedSubView(((ViewGroup) view).getChildAt(i))); } } return views; } /** * Select all {@link View#INVISIBLE invisible}, {@link View#GONE gone}, and 0-alpha views within the * view hierarchy rooted at the given view * @param v the view hierarchy in which to search * @return a list the found views */ private List<View> recursivelySelectHidden(View v) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectHidden(((ViewGroup) v).getChildAt(i))); } } if (v.getVisibility() == View.INVISIBLE || v.getVisibility() == View.GONE || ViewHelper.getAlpha(v) == 0) list.add(v); return list; } /** * Select all {@link View#VISIBLE visible} and 1-alpha views within the given view hierarchy * @param v the view to search in * @return a list the found views */ private List<View> recursivelySelectVisible(View v) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectVisible(((ViewGroup) v).getChildAt(i))); } } if (v.getVisibility() == View.VISIBLE || ViewHelper.getAlpha(v) == 1) list.add(v); return list; } /** * Select all {@link View#INVISIBLE invisible}, {@link View#GONE gone}, and 0-alpha views within * the view hierarchy of the currently selected views * @return a droidQuery Object containing the found views */ public $ selectHidden() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectHidden(view)); } return $.with(context, subviews); } /** * Select all {@link View#VISIBLE visible} and 1-alpha views within the view hierarchy * of the currenly selected views * @return a droidQuery Object containing the found views */ public $ selectVisible() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectVisible(view)); } return $.with(context, subviews); } /** * Set the current selection to the view with the given id * @param id the id of the view to manipulate * @return this * @see #ids(int...) */ public $ id(int id) { View view = this.findViewById(id); if (view != null) { this.views.clear(); this.rootView = view; this.views.add(view); } return this; } /** * Set the tag for all selected Views * @param tag the Object to set as the tag, or {@code null} to clear the tag * @return this */ public $ tag(Object tag) { for (int i = 0; i < views.size(); i++) { views.get(i).setTag(tag); } return this; } /** * Get the tag for the first selected view * @return the tag for the first view in the current selection */ public Object tag() { return view(0).getTag(); } /** * Set the current selection to the set of views with the given id. * @param ids * @return * @see #id(int) */ public $ ids(int... ids) { this.views.clear(); this.rootView = findViewById(ids[0]); views.add(rootView); for (int i = 1; i < ids.length; i++) { this.views.add(findViewById(ids[i])); } return this; } /** * Selects all {@link ImageView}s within the currently selected view hierarchies * @return a droidQuery Object containing the found {@code ImageView}s */ public $ selectImages() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectByType(view, ImageView.class)); } return $.with(context, subviews); } /** * Selects all views within the given current selection that are the single * children of their parent views * @param v the view whose hierarchy will be checked * @return a list of the found views. */ private List<View> recursivelySelectOnlyChilds(View v) { List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectOnlyChilds(((ViewGroup) v).getChildAt(i))); } } if (v.getParent() instanceof ViewGroup && ((ViewGroup) v.getParent()).getChildCount() == 1) list.add(v); return list; } /** * Selects all views within the current selection that are the single children of their * parent views * @return a droidQuery Object containing the found views. */ public $ selectOnlyChilds() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectOnlyChilds(view)); } return $.with(context, subviews); } /** * Selects all views within the given current selection that are the nth * child of their parent views * @param v the view whose hierarchy will be checked * @param n the index to search. Note that a value of 1 will search the 0th child. * @return a list of the found views. */ private List<View> recursivelySelectNthChilds(View v, final int n) { final List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectNthChilds(((ViewGroup) v).getChildAt(i), n)); } } $.with(v).siblings(new Function() { @Override public void invoke($ droidQuery, Object... params) { int index = (Integer) params[0]; if (index == n) list.add(droidQuery.view(0)); } }); return list; } /** * Selects all views within the current selection that are the nth child of their parent views * @param n the index to search. Note that a value of 1 will search the 0th child. * @return a droidQuery Object containing the found views. */ public $ selectNthChilds(int n) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectNthChilds(view, n)); } return $.with(context, subviews); } /** * Selects all views within the given current selection that are the nth-from-the-end * child of their parent views * @param v the view whose hierarchy will be checked * @param n the index to search. Note that a value of 1 will search the 0th child. * @return a list of the found views. */ private List<View> recursivelySelectNthFromEndChilds(View v, final int n) { final List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectNthFromEndChilds(((ViewGroup) v).getChildAt(i), n)); } } final List<View> siblings = new ArrayList<View>(); $.with(v).siblings(new Function() { @Override public void invoke($ droidQuery, Object... params) { siblings.add(droidQuery.view(0)); } }); Collections.reverse(siblings); if (siblings.size() >= n) list.add(siblings.get(n)); return list; } /** * Selects all views within the current selection that are the nth-from-the-end * child of their parent views * @param n the index to search. Note that a value of 1 will search the 0th child. * @return a droidQuery Object containing the found views. */ public $ selectNthFromEndChilds(int n) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectNthFromEndChilds(view, n)); } return $.with(context, subviews); } /** * Selects all views within the given current selection that are the nth * child of type {@code type} of their parent views * @param v the view whose hierarchy will be checked * @param n the index to search. Note that a value of 1 will search the 0th child. * @param type the type of view to select * @return a list of the found views. */ private List<View> recursivelySelectNthChildsOfType(View v, final int n, final Class<?> type) { final List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectNthChildsOfType(((ViewGroup) v).getChildAt(i), n, type)); } } final Count count = new Count(); $.with(v).siblings(new Function() { @Override public void invoke($ droidQuery, Object... params) { if (type.isInstance(droidQuery.view(0))) count.index++; if (count.index == n) list.add(droidQuery.view(0)); } }); return list; } /** * Selects all views within the current selection that are the nth child of type {@code type} of * their parent views * @param n the index to search. Note that a value of 1 will search the 0th child. * @param type the type of view to select * @return a droidQuery Object containing the found views. */ public $ selectNthChildsOfType(int n, Class<?> type) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectNthChildsOfType(view, n, type)); } return $.with(context, subviews); } /** * Selects all views within the current selection that are the nth child of type {@code type} * of their parent views * @param n the index to search. Note that a value of 1 will search the 0th child. * @param className the name of the type of view to select * @return a droidQuery Object containing the found views. */ public $ selectNthChildsOfType(int n, String className) { Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Throwable t) { try { clazz = Class.forName(String.format(Locale.US, "android.widget.%s", className)); } catch (Throwable t2) { try { clazz = Class.forName(String.format(Locale.US, "android.webkit.%s", className)); } catch (Throwable t3) { return null; } } } return selectNthChildsOfType(n, clazz); } /** * Selects all views within the given current selection that are the nth-from-the-end * child of type {@code type} of their parent views * @param v the view whose hierarchy will be checked * @param n the index to search. * @param type the type of view to select * @return a list of the found views. */ private List<View> recursivelySelectNthFromEndChildsOfType(View v, final int n, final Class<?> type) { final List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectNthFromEndChildsOfType(((ViewGroup) v).getChildAt(i), n, type)); } } final List<View> siblings = new ArrayList<View>(); $.with(v).siblings(new Function() { @Override public void invoke($ droidQuery, Object... params) { siblings.add(droidQuery.view(0)); } }); Collections.reverse(siblings); int count = 0; for (int i = 0; i < siblings.size(); i++) { if (type.isInstance(siblings.get(i))) count++; if (count == n) { list.add(siblings.get(i)); break; } } return list; } /** * Selects all views within the current selection that are the nth-from-the-end * child of type {@code type} of their parent views * @param n the index to search. * @param type the type of view to select * @return a droidQuery Object containing the found views. */ public $ selectNthFromEndChildsOfType(int n, Class<?> type) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectNthFromEndChildsOfType(view, n, type)); } return $.with(context, subviews); } /** * Selects all views within the current selection that are the nth-from-the-end * child of type {@code type} of their parent views * @param n the index to search. Note that a value of 1 will search the 0th child. * @param className the name of the type of view to select * @return a droidQuery Object containing the found views. */ public $ selectNthFromEndChildsOfType(int n, String className) { Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Throwable t) { try { clazz = Class.forName(String.format(Locale.US, "android.widget.%s", className)); } catch (Throwable t2) { try { clazz = Class.forName(String.format(Locale.US, "android.webkit.%s", className)); } catch (Throwable t3) { return null; } } } return selectNthFromEndChildsOfType(n, clazz); } /** * Selects all views within the given current selection that are the last * child of their parent views * @param v the view whose hierarchy will be checked * @return a list of the found views. */ private List<View> recursivelySelectLastChilds(View v, final int n) { final List<View> list = new ArrayList<View>(); if (v instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) { list.addAll(recursivelySelectLastChilds(((ViewGroup) v).getChildAt(i), n)); } } final List<View> siblings = new ArrayList<View>(); $.with(v).siblings(new Function() { @Override public void invoke($ droidQuery, Object... params) { siblings.add(droidQuery.view(0)); } }); if (siblings.size() > 0) list.add(siblings.get(siblings.size() - 1)); return list; } /** * Selects all views within the current selection that are the last child of their parent views * @return a droidQuery Object containing the found views. */ public $ selectLastChilds(int n) { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectLastChilds(view, n)); } return $.with(context, subviews); } /** * Selects all views in the current selection that can contain other child views * @return a droidQuery Object containing the found ViewGroups */ public $ selectParents() { List<View> subviews = new ArrayList<View>(); for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); subviews.addAll(recursivelySelectByType(view, ViewGroup.class)); } return $.with(context, subviews); } /** * Select the intersection of this droidQuery's selection with another droidQuery's selection. * This is a costly operation, with a time-complexity of O(n^2). * @param other$ the droidQuery which which to intersect * @return a new droidQuery instance containing a selection of Views only found in both droidQuery instances. */ public $ intersect($ other$) { List<View> a = new ArrayList<View>(views); List<View> b = new ArrayList<View>(other$.views); List<View> x = new ArrayList<View>(); for (View v0 : a) { for (View v1 : b) { if (v0.equals(v1)) { x.add(v0); continue; } } } return $.with(x); } /** * Select the union of this droidQuery's selection with another droidQuery's selection. * @param other$ the droidQuery which which to merge * @return a new droidQuery instance containing a selection of all Views found in each droidQuery instances. */ public $ union($ other$) { List<View> a = new ArrayList<View>(views); List<View> b = new ArrayList<View>(other$.views); for (View v : b) { if (!a.contains(v)) a.add(v); } return $.with(a); } /** * Select the intersection two droidQuery's selections. * This is a costly operation, with a time-complexity of O(n^2). * @param a * @param b * @return a new droidQuery instance containing a selection of Views only found in both droidQuery instances. */ public static $ intersect($ a, $ b) { List<View> _a = new ArrayList<View>(a.views); List<View> _b = new ArrayList<View>(b.views); List<View> x = new ArrayList<View>(); for (View v0 : _a) { for (View v1 : _b) { if (v0.equals(v1)) { x.add(v0); continue; } } } return $.with(x); } /** * Select the union of two droidQuery's selections * @param a * @param b * @return a new droidQuery instance containing a selection of all Views found in each droidQuery instances. */ public static $ union($ a, $ b) { List<View> _a = new ArrayList<View>(a.views); List<View> _b = new ArrayList<View>(b.views); for (View v : _b) { if (!_a.contains(v)) _a.add(v); } return $.with(_a); } /////Extensions /** * Add an extension with the reference <em>name</em> * @param name String used by the {@link #ext(String)} method for calling this extension * @param clazz the name of the subclass of {@link $Extension} that will be mapped to {@code name}. * Calling {@code $.with(this).ext(myExtension); } will now create a new instance of the given * {@code clazz}, passing in {@code this} instance of <em>$</em>, then calling the {@code invoke} * method. * @throws ClassNotFoundException if {@code clazz} is not a valid class name, or if it is not a * subclass of {@code $Extension}. * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalAccessException * @throws InstantiationException * @note be aware that there is no check for if this extension overwrites a different extension. */ public static void extend(String name, String clazz) throws ClassNotFoundException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException { Class<?> _class = Class.forName(clazz); Class<?> _super = _class.getSuperclass(); if (_super == null || _super != $Extension.class) { throw new ClassNotFoundException("clazz must be subclass of $Extension!"); } Constructor<?> constructor = _class.getConstructor(new Class<?>[] { $.class }); extensions.put(name, constructor); } /** * Add an extension with the reference <em>name</em> * @param name String used by the {@link #ext(String)} method for calling this extension * @param clazz subclass of {@link $Extension} that will be mapped to {@code name}. * Calling {@code $.with(this).ext(MyExtension.class); } will now create a new instance of the given * {@code clazz}, passing in {@code this} instance of <em>$</em>, then calling the {@code invoke} * method. * @throws ClassNotFoundException if {@code clazz} is not a valid class name, or if it is not a * subclass of {@code $Extension}. * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalAccessException * @throws InstantiationException * @note be aware that there is no check for if this extension overwrites a different extension. */ public static void extend(String name, Class<?> clazz) throws ClassNotFoundException, SecurityException, NoSuchMethodException { Class<?> _super = clazz.getSuperclass(); if (_super == null || _super != $Extension.class) { throw new ClassNotFoundException("clazz must be subclass of $Extension!"); } Constructor<?> constructor = clazz.getConstructor(new Class<?>[] { $.class }); extensions.put(name, constructor); } /** * Load the extension with the name defined in {@link #extend(String, String)} * @param extension the name of the extension to load * @return the new extension instance */ public $Extension ext(String extension, Object... args) { Constructor<?> constructor = extensions.get(extension); try { $Extension $e = ($Extension) constructor.newInstance(this); $e.invoke(args); return $e; } catch (Throwable t) { t.printStackTrace(); return null; } } /////File IO /** * Write a byte stream to file * @param s the bytes to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new data to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. */ public void write(byte[] s, FileLocation path, String fileName, boolean append, boolean async) { write(s, path, fileName, append, async, null, null); } /** * Write a String to file * @param s the String to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new String to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. */ public void write(String s, FileLocation path, String fileName, boolean append, boolean async) { write(s.getBytes(), path, fileName, append, async, null, null); } /** * Write a String to file, and execute functions once complete. * @param s the String to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new String to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. * @param success Function to invoke on a successful file-write. Parameters received will be: * <ol> * <li>the String to write * <li>the File that was written (to) * </ol> * @param error Function to invoke on a file I/O error. Parameters received will be: * <ol> * <li>the String to write * <li>the String reason * </ol> */ public void write(final String s, final FileLocation path, final String fileName, boolean append, boolean async, final Function success, Function error) { boolean hasWritePermissions = false; try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (info.requestedPermissions != null) { for (String p : info.requestedPermissions) { if (p.equals("android.permission.WRITE_EXTERNAL_STORAGE")) { hasWritePermissions = true; break; } } } } catch (Exception e) { if (error != null) { error.invoke(this, s, "Invalid Project Package!"); } return; } if (!hasWritePermissions) { if (error != null) { error.invoke(this, s, "You do not have file write privelages. Add the android.permission.WRITE_EXTERNAL_STORAGE permission to your Android Manifest."); } return; } File logFile; if (path == FileLocation.INTERNAL) { if (fileName.contains("\\")) { if (error != null) { error.invoke(this, s, "Internal file names cannot include a path separator. Aborting."); } return; } try { if (fileObservers == null) { fileObservers = new ArrayList<LogFileObserver>(); } LogFileObserver o = new LogFileObserver(fileName, new Runnable() { @Override public void run() { if (success != null) success.invoke($.this, s, new File( String.format(Locale.US, "%s/%s", context.getFilesDir().getName(), fileName))); } }); fileObservers.add(o); o.startWatching(); FileOutputStream fw = context.openFileOutput(fileName, (append ? Context.MODE_APPEND : Context.MODE_PRIVATE)); fw.write(s.getBytes()); fw.close(); } catch (Throwable t) { t.printStackTrace(); } return; } else if (path == FileLocation.CACHE) { String storageDir = context.getCacheDir().toString(); logFile = new File(String.format(Locale.US, "%s/%s", storageDir, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else if (path == FileLocation.DATA) { String storageDir = Environment.getExternalStorageDirectory().toString(); String mainDirName = String.format(Locale.US, "%s/Android/data/%s", storageDir, context.getPackageName()); logFile = new File(String.format(Locale.US, "%s/%s", mainDirName, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else if (path == FileLocation.CUSTOM) { logFile = new File(fileName); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else //external (default) { String storageDir = Environment.getExternalStorageDirectory().toString(); String mainDirName = String.format(Locale.US, "%s/%s", storageDir, context.getPackageName()); logFile = new File(String.format(Locale.US, "%s/%s", mainDirName, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } try { if (fileObservers == null) { fileObservers = new ArrayList<LogFileObserver>(); } final File fLogFile = logFile; LogFileObserver o = new LogFileObserver(logFile, new Runnable() { @Override public void run() { if (success != null) success.invoke($.this, s, fLogFile); } }); fileObservers.add(o); o.startWatching(); FileOutputStream os = new FileOutputStream(logFile, append); os.write(s.getBytes()); os.close(); } catch (Exception e) { e.printStackTrace(); } } /** * Write a byte stream to file, and execute functions once complete. * @param s the bytes to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new bytes to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. * @param success Function to invoke on a successful file-write. Parameters received will be: * <ol> * <li>the byte[] to write * <li>the path to the file * </ol> * @param error Function to invoke on a file I/O error. Parameters received will be: * <ol> * <li>the byte[] to write * <li>the path to the file * </ol> */ public void write(final byte[] s, final FileLocation path, String fileName, boolean append, boolean async, final Function success, Function error) { boolean hasWritePermissions = false; try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (info.requestedPermissions != null) { for (String p : info.requestedPermissions) { if (p.equals("android.permission.WRITE_EXTERNAL_STORAGE")) { hasWritePermissions = true; break; } } } } catch (Exception e) { if (error != null) { error.invoke(this, s, path, "Invalid Project Package!"); } return; } if (!hasWritePermissions) { if (error != null) { error.invoke(this, s, path, "You do not have file write privelages. Add the android.permission.WRITE_EXTERNAL_STORAGE permission to your Android Manifest."); } return; } File logFile; if (path == FileLocation.INTERNAL) { if (fileName.contains("\\")) { if (error != null) { error.invoke(this, s, path, "Internal file names cannot include a path separator. Aborting."); } return; } try { if (fileObservers == null) { fileObservers = new ArrayList<LogFileObserver>(); } LogFileObserver o = new LogFileObserver(fileName, new Runnable() { @Override public void run() { if (success != null) success.invoke($.this, s, path); } }); fileObservers.add(o); o.startWatching(); FileOutputStream fw = context.openFileOutput(fileName, (append ? Context.MODE_APPEND : Context.MODE_PRIVATE)); fw.write(s); fw.close(); } catch (Throwable t) { t.printStackTrace(); } return; } else if (path == FileLocation.CACHE) { String storageDir = context.getCacheDir().toString(); logFile = new File(String.format(Locale.US, "%s/%s", storageDir, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else if (path == FileLocation.DATA) { String storageDir = Environment.getExternalStorageDirectory().toString(); String mainDirName = String.format(Locale.US, "%s/Android/data/%s", storageDir, context.getPackageName()); logFile = new File(String.format(Locale.US, "%s/%s", mainDirName, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else if (path == FileLocation.CUSTOM) { logFile = new File(fileName); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } else //external (default) { String storageDir = Environment.getExternalStorageDirectory().toString(); String mainDirName = String.format(Locale.US, "%s/%s", storageDir, context.getPackageName()); logFile = new File(String.format(Locale.US, "%s/%s", mainDirName, fileName)); //make the parent directory if it does not exist logFile.getParentFile().mkdirs(); } try { if (fileObservers == null) { fileObservers = new ArrayList<LogFileObserver>(); } LogFileObserver o = new LogFileObserver(logFile, new Runnable() { @Override public void run() { if (success != null) success.invoke($.this, s, path); } }); fileObservers.add(o); o.startWatching(); FileOutputStream os = new FileOutputStream(logFile, append); os.write(s); os.close(); } catch (Exception e) { e.printStackTrace(); } } ////Utilities /** * Convert an Object Array to a JSONArray * @param array an Object[] containing any of: JSONObject, JSONArray, String, Boolean, Integer, * Long, Double, NULL, or null. May not include NaNs or infinities. Unsupported values are not * permitted and will cause the JSONArray to be in an inconsistent state. * @return the newly-created JSONArray Object */ public static JSONArray makeArray(Object[] array) { JSONArray json = new JSONArray(); for (Object obj : array) { json.put(obj); } return json; } /** * Convert a JSONArray to an Object Array * @param array the array to convert * @return the converted array */ public static Object[] makeArray(JSONArray array) { Object[] obj = new Object[array.length()]; for (int i = 0; i < array.length(); i++) { try { obj[i] = array.get(i); } catch (Throwable t) { obj[i] = JSONObject.NULL; } } return obj; } /** * Converts a JSON String to a Map * @param json the String to convert * @return a Key-Value Mapping of attributes declared in the JSON string. * @throws JSONException thrown if the JSON string is malformed or incorrectly written */ public static Map<String, ?> map(String json) throws JSONException { return map(new JSONObject(json)); } /** * Convert a {@code JSONObject} to a Map * @param json the JSONObject to parse * @return a Key-Value mapping of the Objects set in the JSONObject * @throws JSONException if the JSON is malformed */ public static Map<String, ?> map(JSONObject json) throws JSONException { @SuppressWarnings("unchecked") Iterator<String> iterator = json.keys(); Map<String, Object> map = new HashMap<String, Object>(); while (iterator.hasNext()) { String key = iterator.next(); try { Object value = json.get(key); map.put(key, value); } catch (JSONException e) { throw e; } catch (Throwable t) { if (key != null) Log.w("AjaxOptions", "Could not set value " + key); else throw new NullPointerException("Iterator reference is null."); } } return map; } /** * Shortcut method for creating a Map of Key-Value pairings. For example:<br> * $.map($.entry(0, "Zero"), $.entry(1, "One"), $.entry(2, "Two")); * @param entries the MapEntry Objects used to populate the map. * @return a new map with the given entries * @see #entry(Object, Object) */ public static Map<?, ?> map(Entry<?, ?>... entries) { return QuickMap.qm(entries); } /** * Shortcut method for creating a Key-Value pairing. For example:<br> * $.map($.entry(0, "Zero"), $.entry(1, "One"), $.entry(2, "Two")); * @param key the key * @param value the value * @return the Key-Value pairing Object * @see #map(Entry...) */ public static Entry<?, ?> entry(Object key, Object value) { return QuickEntry.qe(key, value); } /** * Merges the contents of two arrays together into the first array. */ public static void merge(Object[] array1, Object[] array2) { Object[] newArray = new Object[array1.length + array2.length]; for (int i = 0; i < array1.length; i++) { newArray[i] = array1[i]; } for (int i = 0; i < array2.length; i++) { newArray[i] = array2[i]; } array1 = newArray; } /** * @return a new Function that does nothing when invoked */ public static Function noop() { return new Function() { @Override public void invoke($ droidQuery, Object... args) { } }; } /** * @return the current time, in milliseconds */ public static long now() { return new Date().getTime(); } /** * Parses a JSON string into a JSONObject or JSONArray * @param json the String to parse * @return JSONObject or JSONArray (depending on the given string) if parse succeeds. Otherwise {@code null}. */ public static Object parseJSON(String json) { try { if (json.trim().startsWith("{")) return new JSONObject(json); else return new JSONArray(json); } catch (JSONException e) { return null; } } /** * Parses XML into an XML Document * @param xml the XML to parse * @return XML Document if parse succeeds. Otherwise {@code null}. */ public static Document parseXML(String xml) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { return factory.newDocumentBuilder().parse(xml); } catch (Throwable t) { return null; } } /** * Parses HTML into a {@link Spanned} Object * @param html the HTML to parse * @return the Spanned Object created from the given HTML */ public static Spanned parseHTML(String html) { return Html.fromHtml(html); } /////AJAX /** * Perform a new Ajax Task using the AjaxOptions set in the given Key-Value Map * @param options {@link AjaxOptions} options */ public static void ajax(Map<String, Object> options) { ajax(new JSONObject(options)); } /** * Perform a new Ajax Task using the given JSON string to configure the {@link AjaxOptions} * @param options {@link AjaxOptions} as a JSON String */ public static void ajax(String options) { try { ajax(new JSONObject(options)); } catch (Throwable t) { throw new NullPointerException("Could not parse JSON!"); } } /** * Perform a new Ajax Task using the given JSONObject to configure the {@link AjaxOptions} * @param options {@link AjaxOptions} as a JSONObject Object */ public static void ajax(JSONObject options) { try { ajax(new AjaxOptions(options)); } catch (Throwable t) { Log.e("droidQuery", "Could not complete ajax task!", t); } } /** * Perform an Ajax Task using the given {@code AjaxOptions} * @param options the options to set for the Ajax Task */ public static void ajax(AjaxOptions options) { try { if (options.usesNewAPI()) new Ajax(options).execute(); else new AjaxTask(options).execute(); } catch (Throwable t) { Log.e("droidQuery", "Could not complete ajax task!", t); } } /** * Perform an Ajax Task. This is usually done as the result of an Ajax Error * @param request the request * @param options the configuration * @see AjaxTask.AjaxError * @deprecated This will make a request using the legacy AjaxTask, which is no longer supported. * Using {@link #ajax(HttpURLConnection, AjaxOptions)} is recommended. */ public static void ajax(HttpUriRequest request, AjaxOptions options) { new AjaxTask(request, options).execute(); } /** * Perform an Ajax Task. This is usually done as the result of an Ajax Error. Note that this uses new Ajax methods. * @param request the request * @param options the configuration * @see AjaxTask.AjaxError */ public static void ajax(HttpURLConnection request, AjaxOptions options) { new Ajax(request, options).execute(); } ///////ajax shortcut methods /** * Shortcut method to use the default AjaxOptions to perform an Ajax GET request * @param url the URL to access * @param data the data to pass, if any * @param success the Function to invoke once the task completes successfully. * @param dataType the type of data to expect as a response from the URL. See * {@link AjaxOptions#dataType()} for a list of available data types */ public static void get(String url, Object data, Function success, String dataType) { $.ajax(new AjaxOptions().url(url).data(data).success(success).dataType(dataType)); } /** * Shortcut method to use the default Ajax Options to perform an Ajax GET request and receive * a JSON-formatted response * @param url the URL to access * @param data the data to send, if any * @param success Function to invoke once the task completes successfully. */ public static void getJSON(String url, Object data, Function success) { get(url, data, success, "JSON"); } /** * Shortcut method to use the default Ajax Options to perform an Ajax GET request and receive * a Script response * @param url the URL to access * @param data the data to send, if any * @param success Function to invoke once the task completes successfully. * @see {@link ScriptResponse} */ public static void getScript(String url, Function success) { $.ajax(new AjaxOptions().url(url).success(success).dataType("SCRIPT")); } /** * Shortcut method to use the default Ajax Options to perform an Ajax POST request * @param url the URL to access * @param data the data to post * @param success Function to invoke once the task completes successfully. * @param dataType the type of data to expect as a response from the URL. See * {@link AjaxOptions#dataType()} for a list of available data types */ public static void post(String url, Object data, Function success, String dataType) { $.ajax(new AjaxOptions().type("POST").url(url).data(data).success(success).dataType(dataType)); } /** * Register an event to invoke a Function every time a global Ajax Task completes * @param complete the Function to call. */ public static void ajaxComplete(Function complete) { EventCenter.bind("ajaxComplete", complete, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task completes. * @see #ajaxComplete(Function) */ public static void ajaxComplete() { EventCenter.trigger("ajaxComplete", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task receives an error * @param error the Function to call. */ public static void ajaxError(Function error) { EventCenter.bind("ajaxError", error, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task receives an error. * @see #ajaxError(Function) */ public static void ajaxError() { EventCenter.trigger("ajaxError", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task sends data * @param send the Function to call. */ public static void ajaxSend(Function send) { EventCenter.bind("ajaxSend", send, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task sends data. * @see #ajaxSend(Function) */ public static void ajaxSend() { EventCenter.trigger("ajaxSend", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task begins, if no other * global task is running. * @param start the Function to call. */ public static void ajaxStart(Function start) { EventCenter.bind("ajaxStart", start, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task begins, if * no other global task is running. * @see #ajaxStart(Function) */ public static void ajaxStart() { EventCenter.trigger("ajaxStart", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task stops, if it was * the last global task running * @param stop the Function to call. */ public static void ajaxStop(Function stop) { EventCenter.bind("ajaxStop", stop, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task stops, if it was * the last global task running * @see #ajaxStop(Function) */ public static void ajaxStop() { EventCenter.trigger("ajaxStop", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task completes successfully. * @param start the Function to invoke. */ public static void ajaxSuccess(Function success) { EventCenter.bind("ajaxSuccess", success, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task completes * successfully. * @see #ajaxSuccess(Function) */ public static void ajaxSuccess() { EventCenter.trigger("ajaxSuccess", null, null); } /** * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax(). * Note that this will be run in the background thread, so any changes to the UI must be made through a call to Activity.runOnUiThread(). * @param prefilter {@link Function} that will receive one Map argument with the following contents: * <ul> * <li>"options" : AjaxOptions for the request * <li>"request" : HttpClient request Object (if Ajax uses the deprecated {@link AjaxTask} class - otherwise this will always be {@code null}) * <li>"connection" : HttpURLConnection Object (only if request is null - if not, this object will not be mapped). * </ul> * */ public static void ajaxPrefilter(Function prefilter) { EventCenter.bind("ajaxPrefilter", prefilter, null); } /** * Setup global options * @param options */ public static void ajaxSetup(AjaxOptions options) { AjaxOptions.ajaxSetup(options); } /** * Clears async task queue */ public static void ajaxKillAll() { AjaxTask.killTasks(); Ajax.killTasks(); } /** * Load data from the server and place the returned HTML into the matched element * @param url A string containing the URL to which the request is sent. * @param data A plain object or string that is sent to the server with the request. * @param complete A callback function that is executed when the request completes. Will receive * two arguments: 1. response text, 2. text status */ public void load(String url, Object data, final Function complete) { $.ajax(new AjaxOptions().url(url).data(data).complete(new Function() { @Override public void invoke($ droidQuery, Object... params) { $.this.html(params[0].toString()); complete.invoke($.this, params); } })); } /** * creates a text string in standard URL-encoded notation * @return */ public String serialize() { StringBuilder serial = new StringBuilder(); boolean isFirst = true; synchronized (views) { for (int i = 0; i < views.size(); i++) { View v = views.get(i); if (v.getId() != 0) { String name = v.getContext().getResources().getResourceEntryName(v.getId()); Object value = $.with(v).val(); if (value != null) { if (isFirst) { serial.append(String.format(Locale.US, "%s=%s", name, value.toString())); isFirst = false; } else serial.append(String.format(Locale.US, "&%s=%s", name, value.toString())); } } } } return serial.toString(); } /** * creates a JavaScript array of objects, ready to be encoded as a JSON string * @return */ public String serializeArray() { StringBuilder serial = new StringBuilder("]"); boolean isFirst = true; synchronized (views) { for (int i = 0; i < views.size(); i++) { View v = views.get(i); if (v.getId() != 0) { String name = v.getContext().getResources().getResourceEntryName(v.getId()); Object value = $.with(v).val(); if (value != null) { if (isFirst) { serial.append(String.format(Locale.US, "{\"name\":\"%s\",\"value\":%s}", name, (value instanceof String ? String.format(Locale.US, "%s", value) : value.toString()))); isFirst = false; } else { serial.append(String.format(Locale.US, ",{\"name\":\"%s\",\"value\":%s}", name, (value instanceof String ? String.format(Locale.US, "%s", value) : value.toString()))); } } } } } serial.append("]"); return serial.toString(); } //// Convenience /** * Include the html string in the selected views. If a view has a setText method, it is used. Otherwise, * a new TextView is created. This html can also handle image tags for both urls and local files. * Local files should be the name (for example, for R.id.ic_launcher, just use ic_launcher). * @param resourceID the ID of the String resource */ public $ html(int resourceID) { return html(context.getResources().getText(resourceID).toString()); } /** * Include the html string the selected views. If a view has a setText method, it is used. Otherwise, * a new TextView is created. This html can also handle image tags for both urls and local files. * Local files should be the name (for example, for R.id.ic_launcher, just use ic_launcher). * @param html the HTML String to include */ public $ html(String html) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); try { Method m = view.getClass().getMethod("setText", new Class<?>[] { CharSequence.class }); m.invoke(view, (CharSequence) Html.fromHtml(html)); } catch (Throwable t) { if (view instanceof ViewGroup) { try { //no setText method. Try a TextView TextView tv = new TextView(this.context); tv.setBackgroundColor(context.getResources().getColor(android.R.color.transparent)); tv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); ((ViewGroup) view).addView(tv); tv.setText(Html.fromHtml(html, new AsyncImageGetter(tv), null)); } catch (Throwable t2) { //unable to set content Log.w("droidQuery", "unable to set HTML content"); } } else { //unable to set content Log.w("droidQuery", "unable to set textual content"); } } } return this; } /** * Includes the given text string inside of the selected views. If a view has a setText method, it is used * otherwise, if possible, a textview is added as a child to display the text. * @param resourceId resource ID of the text to include */ public $ text(int resourceId) { return text(context.getResources().getText(resourceId)); } /** * Includes the given text string inside of the selected views. If a view has a setText method, it is used * otherwise, if possible, a textview is added as a child to display the text. * @param text the text to include */ public $ text(CharSequence text) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); try { Method m = view.getClass().getMethod("setText", new Class<?>[] { CharSequence.class }); m.invoke(view, text); } catch (Throwable t) { if (view instanceof ViewGroup) { try { //no setText method. Try a TextView TextView tv = new TextView(this.context); tv.setBackgroundColor(context.getResources().getColor(android.R.color.transparent)); tv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); tv.setText(text); ((ViewGroup) view).addView(tv); } catch (Throwable t2) { //unable to set content Log.w("droidQuery", "unable to set textual content"); } } else { //unable to set content Log.w("droidQuery", "unable to set textual content"); } } } return this; } /** * Includes the given image inside of the selected views. If a view is an `ImageView`, its image * is set. Otherwise, the background image of the view is set. * @param resourceId the resource ID of the drawable to display * @return this */ public $ image(int resourceId) { for (View v : views) { if (v instanceof ImageView) { ((ImageView) v).setImageResource(resourceId); } else { v.setBackgroundResource(resourceId); } } return this; } /** * Includes the given image inside of the selected views. If a view is an `ImageView`, its image * is set. Otherwise, the background image of the view is set. * @param image the bitmap image to include * @return this */ public $ image(Bitmap image) { for (View v : views) { if (v instanceof ImageView) { ((ImageView) v).setImageBitmap(Bitmap.createBitmap(image)); } else { v.setBackgroundDrawable(new BitmapDrawable(image)); } } return this; } /** * Includes the given image inside of the selected views. If a view is an `ImageView`, its image * is set. Otherwise, the background image of the view is set. * @param image the drawable image to include * @return this */ public $ image(Drawable image) { for (View v : views) { if (v instanceof ImageView) { ((ImageView) v).setImageDrawable(image); } else { v.setBackgroundDrawable(image); } } return this; } /** * For `ImageView`s, this will set the image to the given asset or url. Otherwise, it will set the * background image for the selected views. * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ image(String source) { return image(source, -1, -1, null); } /** * For `ImageView`s, this will set the image to the given asset or url. Otherwise, it will set the * background image for the selected views. * @param source asset path, file path (starting with "file://") or URL to image * @param width specifies the output bitmap width * @param height specifies the output bitmap height * @param error if the given source is a file or asset, this receives a droidQuery wrapping the * current context and the {@code Throwable} error. Otherwise, this will receive an * Ajax error. * @return this * @see AjaxOptions#error(Function) */ public $ image(final String source, int width, int height, final Function error) { if (source.startsWith("file://")) { try { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.ARGB_8888; if (width >= 0) opt.outWidth = width; if (height >= 0) opt.outHeight = height; Bitmap bitmap = BitmapFactory.decodeFile(source.substring(6), opt); for (View v : views) { if (v instanceof ImageView) { try { ((ImageView) v).setImageBitmap(Bitmap.createBitmap(bitmap)); } catch (Throwable t) { if (error != null) error.invoke($.with(context), t); } } else { v.setBackgroundDrawable(new BitmapDrawable(Bitmap.createBitmap(bitmap))); } } } catch (Throwable t) { if (error != null) { error.invoke($.with(context), t); } } } else if (URLUtil.isValidUrl(source)) { AjaxOptions options = new AjaxOptions().url(source).type("GET").dataType("image").context(context) .global(false).redundancy(Redundancy.RESPOND_TO_ALL_LISTENERS).success(new Function() { @Override public void invoke($ droidQuery, Object... params) { Bitmap bitmap = (Bitmap) params[0]; for (View v : views) { if (v instanceof ImageView) { try { ((ImageView) v).setImageBitmap(Bitmap.createBitmap(bitmap)); } catch (Throwable t) { if (error != null) error.invoke($.with(context), t); } } else { v.setBackgroundDrawable(new BitmapDrawable(Bitmap.createBitmap(bitmap))); } } } }); if (error != null) { options.error(error); } if (width >= 0) { options.imageWidth(width); } if (height >= 0) { options.imageHeight(height); } $.ajax(options); } else { try { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inSampleSize = 1; opt.inPurgeable = true; opt.inInputShareable = false; if (width >= 0) opt.outWidth = width; if (height >= 0) opt.outHeight = height; Bitmap bitmap = BitmapFactory.decodeStream(context.getAssets().open(source), new Rect(0, 0, 0, 0), opt); for (View v : views) { if (v instanceof ImageView) { try { ((ImageView) v).setImageBitmap(Bitmap.createBitmap(bitmap)); } catch (Throwable t) { if (error != null) error.invoke($.with(context), t); } } else { v.setBackgroundDrawable(new BitmapDrawable(Bitmap.createBitmap(bitmap))); } } } catch (Throwable t) { if (error != null) { error.invoke($.with(context), t); } } } return this; } /** * Iterates through the selected views and sets the images to the given images (in order) * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ image(final List<String> sources) { this.each(new Function() { @Override public void invoke($ droidQuery, Object... params) { droidQuery.image(sources.get((Integer) params[0])); } }); return this; } /** * Iterates through the selected views and sets the images to the given images (in order) * @param sources the file paths or URLs to set * @param width the output width of the image * @param height the output height of the image * @param error if the given source is a file or asset, this receives a droidQuery wrapping the * current context and the {@code Throwable} error. Otherwise, this will receive an * Ajax error. * @return this * @see AjaxOptions#error(Function) */ public $ image(final List<String> sources, final int width, final int height, final Function error) { this.each(new Function() { @Override public void invoke($ droidQuery, Object... params) { droidQuery.image(sources.get((Integer) params[0]), width, height, error); } }); return this; } /** * Adds an Image over each selected View as a mask. * In most cases, this mask can be retrieved by querying siblings. For example: * <pre> * ImageView mask = (ImageView) $.with(myView).parent().selectChildren().selectImages().view(0); * </pre> * @param resourceId the resource ID of the mask drawable * @return this */ public $ mask(int resourceId) { for (View v : views) { ImageView image = new ImageView(context); image.setImageResource(resourceId); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } return this; } /** * Adds an Image over each selected View as a mask. * In most cases, this mask can be retrieved by querying siblings. For example: * <pre> * ImageView mask = (ImageView) $.with(myView).parent().selectChildren().selectImages().view(0); * </pre> * @param mask the bitmap to draw * @return this */ public $ mask(Bitmap mask) { for (View v : views) { ImageView image = new ImageView(context); image.setImageBitmap(mask); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } return this; } /** * Adds an Image over each selected View as a mask. * In most cases, this mask can be retrieved by querying siblings. For example: * <pre> * ImageView mask = (ImageView) $.with(myView).parent().selectChildren().selectImages().view(0); * </pre> * @param mask the drawable to draw * @return this */ public $ mask(Drawable mask) { for (View v : views) { ImageView image = new ImageView(context); image.setImageDrawable(mask); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } return this; } /** * Adds an Image over each selected View as a mask. * In most cases, this mask can be retrieved by querying siblings. For example: * <pre> * ImageView mask = (ImageView) $.with(myView).parent().selectChildren().selectImages().view(0); * </pre> * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ mask(String source) { return mask(source, -1, -1, null); } /** * Adds an Image over each selected View as a mask. * In most cases, this mask can be retrieved by querying siblings. For example: * <pre> * ImageView mask = (ImageView) $.with(myView).parent().selectChildren().selectImages().view(0); * </pre> * @param source asset path, file path (starting with "file://") or URL to image * @param width specifies the output bitmap width * @param height specifies the output bitmap height * @param error if the given source is a file or asset, this receives a droidQuery wrapping the * current context and the {@code Throwable} error. Otherwise, this will receive an * Ajax error. * @return this * @see AjaxOptions#error(Function) */ public $ mask(String source, int width, int height, Function error) { if (source.startsWith("file://")) { try { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.ARGB_8888; if (width >= 0) opt.outWidth = width; if (height >= 0) opt.outHeight = height; Bitmap bitmap = BitmapFactory.decodeFile(source.substring(6), opt); for (View v : views) { ImageView image = new ImageView(context); image.setImageBitmap(Bitmap.createBitmap(bitmap)); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } } catch (Throwable t) { if (error != null) { error.invoke($.with(context), t); } } } else if (URLUtil.isValidUrl(source)) { AjaxOptions options = new AjaxOptions().url(source).type("GET").dataType("image").context(context) .global(false).success(new Function() { @Override public void invoke($ droidQuery, Object... params) { Bitmap bitmap = (Bitmap) params[0]; for (View v : views) { ImageView image = new ImageView(context); image.setImageBitmap(Bitmap.createBitmap(bitmap)); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } } }); if (error != null) { options.error(error); } if (width >= 0) { options.imageWidth(width); } if (height >= 0) { options.imageHeight(height); } $.ajax(options); } else { try { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inSampleSize = 1; opt.inPurgeable = true; opt.inInputShareable = false; if (width >= 0) opt.outWidth = width; if (height >= 0) opt.outHeight = height; Bitmap bitmap = BitmapFactory.decodeStream(context.getAssets().open(source), new Rect(0, 0, 0, 0), opt); for (View v : views) { ImageView image = new ImageView(context); image.setImageBitmap(Bitmap.createBitmap(bitmap)); image.setScaleType(ScaleType.FIT_XY); ViewParent parent = v.getParent(); if (parent != null && parent instanceof ViewGroup) { image.setLayoutParams(v.getLayoutParams()); ((ViewGroup) parent).addView(image); } else if (v instanceof ViewGroup) { image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((ViewGroup) v).addView(image); } } } catch (Throwable t) { if (error != null) { error.invoke($.with(context), t); } } } return this; } /** * Iterates through the selected views and sets the masks to the given images (in order) * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ mask(final List<String> sources) { this.each(new Function() { @Override public void invoke($ droidQuery, Object... params) { droidQuery.mask(sources.get((Integer) params[0])); } }); return this; } /** * Shortcut to show a Toast Message * @param text the text to display * @param duration the Toast Duration * @see Toast#LENGTH_LONG * @see Toast#LENGTH_SHORT */ public void toast(CharSequence text, int duration) { Toast.makeText(context, text, duration).show(); } /** * Shortcut to show a Toast Message * @param context used to show the toast * @param text the text to display * @param duration the Toast Duration * @see Toast#LENGTH_LONG * @see Toast#LENGTH_SHORT */ public static void toast(Context context, CharSequence text, int duration) { Toast.makeText(context, text, duration).show(); } /** * Show an alert. The title will be set to the name of the application. * @param context used to display the alert window * @param text the message to display. * @see #alert(Context, String, String) */ public static void alert(Context context, String text) { alert(context, context.getString( context.getResources().getIdentifier("app_name", "string", context.getPackageName())), text); } /** * Show an alert * @param context used to display the alert window * @param title the title of the alert window. Use {@code null} to show no title * @param text the alert message * @see #alert(Context, String) */ public static void alert(Context context, String title, String text) { AlertDialog alert = new AlertDialog.Builder(context).create(); alert.setTitle(title); alert.setMessage(text); alert.setButton("OK", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alert.show(); } /** * Uses the current context to show an alert dialog. The title will be set to the name of the application. * @param text the alert message. * @see #alert(String, String) */ public void alert(String text) { alert(context.getString( context.getResources().getIdentifier("app_name", "string", context.getPackageName())), text); } /** * Uses the current context to show an alert dialog. * @param title the alert title * @param text the alert message. * @see #alert(String) */ public void alert(String title, String text) { AlertDialog alert = new AlertDialog.Builder(context).create(); alert.setTitle(title); alert.setMessage(text); alert.setButton("OK", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alert.show(); } ////Callbacks /** * A multi-purpose callbacks list object that provides a powerful way to manage callback lists. * Registered callback functions will receive a {@code null} for their <em>droidQuery</em> * variable. To receive a non-{@code null} variable, you must provide a <em>Context</em>. * @return a new instance of {@link Callbacks} * @see #Callbacks(Context) */ public static Callbacks Callbacks() { return new Callbacks(); } /** * A multi-purpose callbacks list object that provides a powerful way to manage callback lists. * If {@code context} is not {@code null}, Functions invoked will receive a non-{@code null} * droidQuery instance using that context. * @return a new instance of {@link Callbacks} */ public static Callbacks Callbacks(Context context) { return new Callbacks(context); } //////CSS-based /** * @return the computed height for the first view in the current selection */ public int height() { return Math.abs(view(0).getBottom() - view(0).getTop()); } /** * Set the height of the selected views * @param height the new height * @return this */ public $ height(int height) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewGroup.LayoutParams params = view.getLayoutParams(); params.height = height; view.setLayoutParams(params); } return this; } /** * @return the computed width for the first view in the current selection */ public int width() { return Math.abs(view(0).getRight() - view(0).getLeft()); } /** * Set the width of the views in the current selection * @param width the new width * @return this */ public $ width(int width) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewGroup.LayoutParams params = view.getLayoutParams(); params.width = width; view.setLayoutParams(params); } return this; } /** * Get the computed height for the first view in the selection, * including padding but not margin. * @return */ public int innerHeight() { return height() - (view(0).getPaddingTop() + view(0).getPaddingBottom()); } /** * @return the computed width for the first view in the current selection, * including padding but not margin. */ public int innerWidth() { return width() - (view(0).getPaddingLeft() + view(0).getPaddingRight()); } /** * @return the computed height for the first view in the current selection, * including padding and margin. */ public int outerHeight() { Object params = view(0).getLayoutParams(); int margin = 0; try { margin += Integer.valueOf(params.getClass().getField("bottomMargin").get(params).toString()); margin += Integer.valueOf(params.getClass().getField("topMargin").get(params).toString()); } catch (Throwable t) { //cannot get margin values } return height() - (view(0).getPaddingTop() + view(0).getPaddingBottom() + margin); } /** * @return the computed width for the first view in the current selection, * including padding and margin. */ public int outerWidth() { Object params = view(0).getLayoutParams(); int margin = 0; try { margin += Integer.valueOf(params.getClass().getField("leftMargin").get(params).toString()); margin += Integer.valueOf(params.getClass().getField("rightMargin").get(params).toString()); } catch (Throwable t) { //cannot get margin values } return width() - (view(0).getPaddingLeft() + view(0).getPaddingRight() + margin); } /** * @return the coordinates of the first view in the current selection. */ public Point offset() { int[] loc = new int[2]; view(0).getLocationOnScreen(loc); return new Point(loc[0], loc[1]); } /** * Gets the coordinates of {@code v} * @param v * @return the coordinates of the given view */ private Point offset(View v) { int[] loc = new int[2]; v.getLocationOnScreen(loc); return new Point(loc[0], loc[1]); } /** * Set the coordinates of each selected view, relative to the document. * @param x the x-coordinate, in pixels * @param y the y-coordinate, in pixels * @return this */ public $ offset(int x, int y) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); Point offset = offset(view); int offsetX = x - offset.x; int offsetY = y - offset.y; Point position = position(view); ViewHelper.setX(view, position.x + offsetX); ViewHelper.setY(view, position.y + offsetY); } return this; } /** * @return the coordinates of the first view in the current selection, * relative to the offset parent. */ public Point position() { return new Point(view(0).getLeft(), view(0).getTop()); } /** * Get the coordinates of the {@code view}, relative to the offset parent * @param view * @return the coordinates of the given view, relative to the offset parent */ private Point position(View view) { return new Point(view.getLeft(), view.getTop()); } /** * Sets the coordinates of each selected view, relative to its offset parent * @param x the x-coordinate * @param y the y-coordinate * @return */ public $ position(int x, int y) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); ViewHelper.setX(view, x); ViewHelper.setY(view, y); } return this; } /** * @return the current horizontal position of the scroll bar the first view of the current selection. */ public int scrollLeft() { return view(0).getScrollX(); } /** * Set the horizontal position of the scroll bar for each view in the current selection * @param position the x position to which to scroll * @return this */ public $ scrollLeft(int position) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.scrollTo(position, view.getScrollY()); } return this; } /** * @return the current vertical position of the scroll bar for the first view of the current selection */ public int scrollTop() { return view(0).getScrollY(); } /** * Set the vertical position of the scroll bar for the currently selected views * @param position the scroll position * @return this */ public $ scrollTop(int position) { for (int i = 0; i < this.views.size(); i++) { View view = this.views.get(i); view.scrollTo(view.getScrollX(), position); } return this; } //////ignoring jQuery data. Doesn't come off as important here. //Timer Functions /** * Schedule a task for single execution after a specified delay. * @param function the task to schedule. Receives no args. Note that the function will be * run on the thread on this method was called. * @param delay amount of time in milliseconds before execution. * @return the created Timer */ public static Timer setTimeout(final Function function, long delay) { Timer t = new Timer(); final Handler h = new Handler(); t.schedule(new TimerTask() { @Override public void run() { h.post(new Runnable() { @Override public void run() { function.invoke(null); } }); } }, delay); return t; } /** * Schedule a task for repeated fixed-rate execution after a specific delay has passed. * @param the task to schedule. Receives no args. Note that the function will be * run on a the thread on which this method was called. * @param delay amount of time in milliseconds before execution. * @return the created Timer */ public static Timer setInterval(final Function function, long delay) { Timer t = new Timer(); final Handler h = new Handler(); t.scheduleAtFixedRate(new TimerTask() { @Override public void run() { h.post(new Runnable() { @Override public void run() { function.invoke(null); } }); } }, 0, delay); return t; } /** * Capitalizes the first letter of the given string. * @param string the string whose first letter should be capitalized * @return the given string with its first letter capitalized * @throws NullPointerException if the string is null or empty */ private String capitalize(String string) { if (string == null || string.trim().length() == 0) throw new NullPointerException("Cannot handle null or empty string"); StringBuilder strBuilder = new StringBuilder(string); strBuilder.setCharAt(0, Character.toUpperCase(strBuilder.charAt(0))); return strBuilder.toString(); } /** * Used for Observing when a log file has been written to. * @author Phil Brown */ class LogFileObserver extends FileObserver { /** the runnable to post when the file is no longer being written to */ private Runnable r; /** * Create the observer * @param path the file to observe * @param postExec the runnable to post when the file is no longer being written to */ public LogFileObserver(String path, Runnable postExec) { super(path, FileObserver.CLOSE_WRITE); r = postExec; } /** * Create the observer * @param file the file to observe * @param postExec the runnable to post when the file is no longer being written to */ public LogFileObserver(File file, Runnable postExec) { super(file.getAbsolutePath(), FileObserver.CLOSE_WRITE); r = postExec; } @Override public void onEvent(int event, String path) { if (event == FileObserver.CLOSE_WRITE) { r.run(); stopWatching(); fileObservers.remove(this); } } } /** * Simple class for allowing final access to an int * @author Phil Brown * @since 3:24:53 PM Dec 5, 2013 * */ private class Count { public int index; public Count() { index = 0; } } }