I want to learn how to handle the image and gesture function in android.
So I read the sample "InteractiveChart" under "Animating a Scroll Gesture" section in Android developer website.
While I read about "onDoubleTap" method in InteractiveLineGraphView.java.
#Override
public boolean onDoubleTap(MotionEvent e) {
mZoomer.forceFinished(true);
if (hitTest(e.getX(), e.getY(), mZoomFocalPoint)) {
mZoomer.startZoom(ZOOM_AMOUNT);
}
ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
return true;
}
I checked the code of Zoomer.
It mainly calls DecelerateInterpolator method and set some variables.
I wonder how can "Zoomer" achieve the double tap zoom function.
Does "DecelerateInterpolator" do the work? Or I just missed something?
public class Zoomer {
/**
* The interpolator, used for making zooms animate 'naturally.'
*/
private Interpolator mInterpolator;
/**
* The total animation duration for a zoom.
*/
private int mAnimationDurationMillis;
/**
* Whether or not the current zoom has finished.
*/
private boolean mFinished = true;
/**
* The current zoom value; computed by {#link #computeZoom()}.
*/
private float mCurrentZoom;
/**
* The time the zoom started, computed using {#link android.os.SystemClock#elapsedRealtime()}.
*/
private long mStartRTC;
/**
* The destination zoom factor.
*/
private float mEndZoom;
public Zoomer(Context context) {
mInterpolator = new DecelerateInterpolator();
mAnimationDurationMillis = context.getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
/**
* Forces the zoom finished state to the given value. Unlike {#link #abortAnimation()}, the
* current zoom value isn't set to the ending value.
*
* #see android.widget.Scroller#forceFinished(boolean)
*/
public void forceFinished(boolean finished) {
mFinished = finished;
}
/**
* Aborts the animation, setting the current zoom value to the ending value.
*
* #see android.widget.Scroller#abortAnimation()
*/
public void abortAnimation() {
mFinished = true;
mCurrentZoom = mEndZoom;
}
/**
* Starts a zoom from 1.0 to (1.0 + endZoom). That is, to zoom from 100% to 125%, endZoom should
* by 0.25f.
*
* #see android.widget.Scroller#startScroll(int, int, int, int)
*/
public void startZoom(float endZoom) {
mStartRTC = SystemClock.elapsedRealtime();
mEndZoom = endZoom;
mFinished = false;
mCurrentZoom = 1f;
}
/**
* Computes the current zoom level, returning true if the zoom is still active and false if the
* zoom has finished.
*
* #see android.widget.Scroller#computeScrollOffset()
*/
public boolean computeZoom() {
if (mFinished) {
return false;
}
long tRTC = SystemClock.elapsedRealtime() - mStartRTC;
if (tRTC >= mAnimationDurationMillis) {
mFinished = true;
mCurrentZoom = mEndZoom;
return false;
}
float t = tRTC * 1f / mAnimationDurationMillis;
mCurrentZoom = mEndZoom * mInterpolator.getInterpolation(t);
return true;
}
/**
* Returns the current zoom level.
*
* #see android.widget.Scroller#getCurrX()
*/
public float getCurrZoom() {
return mCurrentZoom;
}
}
Can someone also recommend some great sample about image and gesture handling? From basic to advanced.....Thanks a lot.
The double tap is actually done in InteractiveLineGraphView view itself
#Override
public boolean onDoubleTap(MotionEvent e) {
mZoomer.forceFinished(true);
if (hitTest(e.getX(), e.getY(), mZoomFocalPoint)) {
mZoomer.startZoom(ZOOM_AMOUNT);
}
ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
return true;
}
Zoomer is just a helper class holding current zoom level
Before posting this question I've searched a lot but could not find any clear answers on this issue.
I have to override default text selection of android webview & show my custom text selection dialog options. I have tried this sample code project.
This sample project works on following devices & emulator :
Acer Iconia a500 tablet : 10 inch : Android OS - 3.0
Acer Iconia a500 tablet : 10 inch : Android OS - 3.2
Samsung Galaxy Tab : 10 inch : Android OS - 4.0
Samsung Galaxy Tab : 7 inch : Android OS - 4.0
Emulator : Skin-WVGA800 : Android OS - 4.1.2
Not working on following devices :
Samsung Galaxy Tab : 10 inch : Android OS - 4.1.2
Samsung Galaxy Tab : 7 inch : Android OS - 4.1.2
On android os version 4.1 & 4.1+ instead of showing my custom text selection option dialog, it shows android system's default action bar for text selection.
I have searched a lot on this, many suggested to use onLongClick() method of the interface
I have already asked a question on this forum please see this link, with answers to this questions I am able to clone onLongClick() event but I can't stop default text selection action bar.
For this scenario I have few questions.
1.Why onLongClick() method stops working for device running on android os version 4.1+ ?
2.How to stop default text selection action bar on long pressing on the text from webview ?
This is my custom webview class.
package com.epubreader.ebook;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Display;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.epubreader.R;
import com.epubreader.drag.DragController;
import com.epubreader.drag.DragLayer;
import com.epubreader.drag.DragListener;
import com.epubreader.drag.DragSource;
import com.epubreader.drag.MyAbsoluteLayout;
import com.epubreader.menu.menuAnimationHelper;
import com.epubreader.textselection.WebTextSelectionJSInterface;
import com.epubreader.textselectionoverlay.ActionItem;
import com.epubreader.textselectionoverlay.QuickAction;
import com.epubreader.textselectionoverlay.QuickAction.OnDismissListener;
public class CustomWebView extends WebView implements WebTextSelectionJSInterface,
OnTouchListener , OnLongClickListener, OnDismissListener, DragListener{
/** The logging tag. */
private static final String TAG = "CustomWebView";
/** Context. */
protected Context ctx;
/** The context menu. */
private QuickAction mContextMenu;
/** The drag layer for selection. */
private DragLayer mSelectionDragLayer;
/** The drag controller for selection. */
private DragController mDragController;
/** The start selection handle. */
private ImageView mStartSelectionHandle;
/** the end selection handle. */
private ImageView mEndSelectionHandle;
/** The selection bounds. */
private Rect mSelectionBounds = null;
/** The previously selected region. */
protected Region lastSelectedRegion = null;
/** The selected range. */
protected String selectedRange = "";
/** The selected text. */
protected String selectedText = "";
/** Javascript interface for catching text selection. */
/** Selection mode flag. */
protected boolean inSelectionMode = false;
/** Flag to stop from showing context menu twice. */
protected boolean contextMenuVisible = false;
/** The current content width. */
protected int contentWidth = 0;
/** Identifier for the selection start handle. */
private final int SELECTION_START_HANDLE = 0;
/** Identifier for the selection end handle. */
private final int SELECTION_END_HANDLE = 1;
/** Last touched selection handle. */
private int mLastTouchedSelectionHandle = -1;
/** Variables for Left & Right Menu ***/
private View menuView;
private LinearLayout toiLay;
private menuAnimationHelper _menuAnimationHelper;
private TocTranslateAnimation _tocTranslateAnimation;
private CustomWebView _customWebView;
public CustomWebView(Context context) {
super(context);
this.ctx = context;
this.setup(context);
}
public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.ctx = context;
this.setup(context);
}
public CustomWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.ctx = context;
this.setup(context);
}
//*****************************************************
//*
//* Touch Listeners
//*
//*****************************************************
private boolean mScrolling = false;
private float mScrollDiffY = 0;
private float mLastTouchY = 0;
private float mScrollDiffX = 0;
private float mLastTouchX = 0;
#Override
public boolean onTouch(View v, MotionEvent event) {
float xPoint = getDensityIndependentValue(event.getX(), ctx) / getDensityIndependentValue(this.getScale(), ctx);
float yPoint = getDensityIndependentValue(event.getY(), ctx) / getDensityIndependentValue(this.getScale(), ctx);
// TODO: Need to update this to use this.getScale() as a factor.
//Log.d(TAG, "onTouch " + xPoint + " , " + yPoint);
closeMenu();
if(event.getAction() == MotionEvent.ACTION_DOWN){
final String startTouchUrl = String.format("javascript:android.selection.startTouch(%f, %f);",
xPoint, yPoint);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
((Activity)this.ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl(startTouchUrl);
}
});
// This two line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
longClickHandler.postDelayed(longClickRunnable,300);
}
else if(event.getAction() == MotionEvent.ACTION_UP){
// Check for scrolling flag
if(!mScrolling){
this.endSelectionMode();
}
// This line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
mScrollDiffX += (xPoint - mLastTouchX);
mScrollDiffY += (yPoint - mLastTouchY);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
// Only account for legitimate movement.
if(Math.abs(mScrollDiffX) > 10 || Math.abs(mScrollDiffY) > 10){
mScrolling = true;
}
// This line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
}
// If this is in selection mode, then nothing else should handle this touch
return false;
}
/**
* Pass References of Left & Right Menu
*/
public void initMenu(LinearLayout _toiLay,View _menuView,menuAnimationHelper menuAnimationHelper,
TocTranslateAnimation tocTranslateAnimation){
toiLay = _toiLay;
menuView = _menuView;
_menuAnimationHelper = menuAnimationHelper;
_tocTranslateAnimation = tocTranslateAnimation;
}
private void closeMenu(){
if(_menuAnimationHelper != null && _menuAnimationHelper.isMenuOpenBool){
_menuAnimationHelper.close(menuView);
_menuAnimationHelper.isMenuOpenBool = false;
}
if(_tocTranslateAnimation != null && _tocTranslateAnimation.isTocListOpenBool){
_tocTranslateAnimation.close(toiLay);
_tocTranslateAnimation.isTocListOpenBool = false;
}
}
public void removeOverlay(){
Log.d("JsHandler", "in java removeOverlay" + mScrolling);
this.endSelectionMode();
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
}
#Override
public boolean onLongClick(View v){
Log.d(TAG, "from webView onLongClick ");
mScrolling = true;
((Activity)this.ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl("javascript:android.selection.longTouch()");
}
});
Toast.makeText(ctx, "Long click is clicked ", Toast.LENGTH_LONG).show();
// Don't let the webview handle it
return true;
}
//*****************************************************
//*
//* Setup
//*
//*****************************************************
ContextMenu.ContextMenuInfo contextMenuInfo;
/**
* Setups up the web view.
* #param context
*/
protected void setup(Context context){
// On Touch Listener
this.setOnTouchListener(this);
this.setClickable(false);
this.setLongClickable(true);
this.setOnLongClickListener(this);
contextMenuInfo = this.getContextMenuInfo();
// Webview setup
this.getSettings().setJavaScriptEnabled(true);
this.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
// Create the selection handles
createSelectionLayer(context);
// Set to the empty region
Region region = new Region();
region.setEmpty();
_customWebView = this;
this.lastSelectedRegion = region;
}
/**
* To clone OnLongClick Listener because its not responding for version 4.1
*/
public Runnable longClickRunnable = new Runnable() {
public void run() {
longClickHandler.sendEmptyMessage(0);
}
};
public Handler longClickHandler = new Handler(){
public void handleMessage(Message m){
_customWebView.loadUrl("javascript:android.selection.longTouch();");
mScrolling = true;
}
};
public WebTextSelectionJSInterface getTextSelectionJsInterface(){
return this;
}
//*****************************************************
//*
//* Selection Layer Handling
//*
//*****************************************************
/**
* Creates the selection layer.
*
* #param context
*/
protected void createSelectionLayer(Context context){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mSelectionDragLayer = (DragLayer) inflater.inflate(R.layout.selection_drag_layer, null);
// Make sure it's filling parent
this.mDragController = new DragController(context);
this.mDragController.setDragListener(this);
this.mDragController.addDropTarget(mSelectionDragLayer);
this.mSelectionDragLayer.setDragController(mDragController);
this.mStartSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.startHandle);
this.mStartSelectionHandle.setTag(new Integer(SELECTION_START_HANDLE));
this.mEndSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.endHandle);
this.mEndSelectionHandle.setTag(new Integer(SELECTION_END_HANDLE));
OnTouchListener handleTouchListener = new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
boolean handledHere = false;
final int action = event.getAction();
// Down event starts drag for handle.
if (action == MotionEvent.ACTION_DOWN) {
handledHere = startDrag (v);
mLastTouchedSelectionHandle = (Integer) v.getTag();
}
return handledHere;
}
};
this.mStartSelectionHandle.setOnTouchListener(handleTouchListener);
this.mEndSelectionHandle.setOnTouchListener(handleTouchListener);
}
/**
* Starts selection mode on the UI thread
*/
private Handler startSelectionModeHandler = new Handler(){
public void handleMessage(Message m){
if(mSelectionBounds == null)
return;
addView(mSelectionDragLayer);
drawSelectionHandles();
int contentHeight = (int) Math.ceil(getDensityDependentValue(getContentHeight(), ctx));
// Update Layout Params
ViewGroup.LayoutParams layerParams = mSelectionDragLayer.getLayoutParams();
layerParams.height = contentHeight;
layerParams.width = contentWidth;
mSelectionDragLayer.setLayoutParams(layerParams);
}
};
/**
* Starts selection mode.
*
* #param selectionBounds
*/
public void startSelectionMode(){
this.startSelectionModeHandler.sendEmptyMessage(0);
}
// Ends selection mode on the UI thread
private Handler endSelectionModeHandler = new Handler(){
public void handleMessage(Message m){
//Log.d("TableContentsWithDisplay", "in endSelectionModeHandler");
removeView(mSelectionDragLayer);
if(getParent() != null && mContextMenu != null && contextMenuVisible){
// This will throw an error if the webview is being redrawn.
// No error handling needed, just need to stop the crash.
try{
mContextMenu.dismiss();
}
catch(Exception e){
}
}
mSelectionBounds = null;
mLastTouchedSelectionHandle = -1;
try {
((Activity)ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl("javascript: android.selection.clearSelection();");
}
});
} catch (Exception e) {
// TODO: handle exception
}
}
};
/**
* Ends selection mode.
*/
public void endSelectionMode(){
this.endSelectionModeHandler.sendEmptyMessage(0);
}
/**
* Calls the handler for drawing the selection handles.
*/
private void drawSelectionHandles(){
this.drawSelectionHandlesHandler.sendEmptyMessage(0);
}
/**
* Handler for drawing the selection handles on the UI thread.
*/
private Handler drawSelectionHandlesHandler = new Handler(){
public void handleMessage(Message m){
MyAbsoluteLayout.LayoutParams startParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mStartSelectionHandle.getLayoutParams();
startParams.x = (int) (mSelectionBounds.left - mStartSelectionHandle.getDrawable().getIntrinsicWidth());
startParams.y = (int) (mSelectionBounds.top - mStartSelectionHandle.getDrawable().getIntrinsicHeight());
// Stay on screen.
startParams.x = (startParams.x < 0) ? 0 : startParams.x;
startParams.y = (startParams.y < 0) ? 0 : startParams.y;
mStartSelectionHandle.setLayoutParams(startParams);
MyAbsoluteLayout.LayoutParams endParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mEndSelectionHandle.getLayoutParams();
endParams.x = (int) mSelectionBounds.right;
endParams.y = (int) mSelectionBounds.bottom;
endParams.x = (endParams.x < 0) ? 0 : endParams.x;
endParams.y = (endParams.y < 0) ? 0 : endParams.y;
mEndSelectionHandle.setLayoutParams(endParams);
}
};
/**
* Checks to see if this view is in selection mode.
* #return
*/
public boolean isInSelectionMode(){
return this.mSelectionDragLayer.getParent() != null;
}
//*****************************************************
//*
//* DragListener Methods
//*
//*****************************************************
/**
* Start dragging a view.
*
*/
private boolean startDrag (View v)
{
// Let the DragController initiate a drag-drop sequence.
// I use the dragInfo to pass along the object being dragged.
// I'm not sure how the Launcher designers do this.
Object dragInfo = v;
mDragController.startDrag (v, mSelectionDragLayer, dragInfo, DragController.DRAG_ACTION_MOVE);
return true;
}
#Override
public void onDragStart(DragSource source, Object info, int dragAction) {
// TODO Auto-generated method stub
}
#Override#SuppressWarnings("deprecation")
public void onDragEnd() {
// TODO Auto-generated method stub
MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams) this.mStartSelectionHandle.getLayoutParams();
MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams) this.mEndSelectionHandle.getLayoutParams();
float scale = getDensityIndependentValue(this.getScale(), ctx);
float startX = startHandleParams.x - this.getScrollX();
float startY = startHandleParams.y - this.getScrollY();
float endX = endHandleParams.x - this.getScrollX();
float endY = endHandleParams.y - this.getScrollY();
startX = getDensityIndependentValue(startX, ctx) / scale;
startY = getDensityIndependentValue(startY, ctx) / scale;
endX = getDensityIndependentValue(endX, ctx) / scale;
endY = getDensityIndependentValue(endY, ctx) / scale;
if(mLastTouchedSelectionHandle == SELECTION_START_HANDLE && startX > 0 && startY > 0){
final String saveStartString = String.format("javascript: android.selection.setStartPos(%f, %f);", startX, startY);
((Activity)ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl(saveStartString);
}
});
}
if(mLastTouchedSelectionHandle == SELECTION_END_HANDLE && endX > 0 && endY > 0){
final String saveEndString = String.format("javascript: android.selection.setEndPos(%f, %f);", endX, endY);
((Activity)ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl(saveEndString);
}
});
}
}
//*****************************************************
//*
//* Context Menu Creation
//*
//*****************************************************
/**
* Shows the context menu using the given region as an anchor point.
* #param region
*/
private void showContextMenu(Rect displayRect){
// Don't show this twice
if(this.contextMenuVisible){
return;
}
// Don't use empty rect
//if(displayRect.isEmpty()){
if(displayRect.right <= displayRect.left){
return;
}
//Copy action item
ActionItem buttonOne = new ActionItem();
buttonOne.setTitle("HighLight");
buttonOne.setActionId(1);
//buttonOne.setIcon(getResources().getDrawable(R.drawable.menu_search));
//Highlight action item
ActionItem buttonTwo = new ActionItem();
buttonTwo.setTitle("Note");
buttonTwo.setActionId(2);
//buttonTwo.setIcon(getResources().getDrawable(R.drawable.menu_info));
ActionItem buttonThree = new ActionItem();
buttonThree.setTitle("Help");
buttonThree.setActionId(3);
//buttonThree.setIcon(getResources().getDrawable(R.drawable.menu_eraser));
// The action menu
mContextMenu = new QuickAction(this.getContext());
mContextMenu.setOnDismissListener(this);
// Add buttons
mContextMenu.addActionItem(buttonOne);
mContextMenu.addActionItem(buttonTwo);
mContextMenu.addActionItem(buttonThree);
//setup the action item click listener
mContextMenu.setOnActionItemClickListener(new QuickAction.OnActionItemClickListener() {
#Override
public void onItemClick(QuickAction source, int pos,
int actionId) {
if (actionId == 1) {
callHighLight();
}
else if (actionId == 2) {
callNote();
}
else if (actionId == 3) {
// Do Button 3 stuff
Log.i(TAG, "Hit Button 3");
}
contextMenuVisible = false;
}
});
this.contextMenuVisible = true;
mContextMenu.show(this, displayRect);
}
private void callHighLight(){
((Activity)this.ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl("javascript:init_txt_selection_event()");
loadUrl("javascript:highlightme_("+0+")");
}
});
}
private void callNote(){
((Activity)this.ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
loadUrl("javascript:init_txt_selection_event()");
loadUrl("javascript:fnGetUserAddedNote('1')");
}
});
}
//*****************************************************
//*
//* OnDismiss Listener
//*
//*****************************************************
/**
* Clears the selection when the context menu is dismissed.
*/
public void onDismiss(){
//clearSelection();
this.contextMenuVisible = false;
}
//*****************************************************
//*
//* Text Selection Javascript Interface Listener
//*
//*****************************************************
/**
* The user has started dragging the selection handles.
*/
public void tsjiStartSelectionMode(){
this.startSelectionMode();
}
/**
* The user has stopped dragging the selection handles.
*/
public void tsjiEndSelectionMode(){
this.endSelectionMode();
}
/**
* The selection has changed
* #param range
* #param text
* #param handleBounds
* #param menuBounds
* #param showHighlight
* #param showUnHighlight
*/#SuppressWarnings("deprecation")
public void tsjiSelectionChanged(String range, String text, String handleBounds, String menuBounds){
try {
//Log.d(TAG, "tsjiSelectionChanged :- handleBounds " + handleBounds);
JSONObject selectionBoundsObject = new JSONObject(handleBounds);
float scale = getDensityIndependentValue(this.getScale(), ctx);
Rect handleRect = new Rect();
handleRect.left = (int) (getDensityDependentValue(selectionBoundsObject.getInt("left"), getContext()) * scale);
handleRect.top = (int) (getDensityDependentValue(selectionBoundsObject.getInt("top"), getContext()) * scale);
handleRect.right = (int) (getDensityDependentValue(selectionBoundsObject.getInt("right"), getContext()) * scale);
handleRect.bottom = (int) (getDensityDependentValue(selectionBoundsObject.getInt("bottom"), getContext()) * scale);
this.mSelectionBounds = handleRect;
this.selectedRange = range;
this.selectedText = text;
JSONObject menuBoundsObject = new JSONObject(menuBounds);
Rect displayRect = new Rect();
displayRect.left = (int) (getDensityDependentValue(menuBoundsObject.getInt("left"), getContext()) * scale);
displayRect.top = (int) (getDensityDependentValue(menuBoundsObject.getInt("top") - 25, getContext()) * scale);
displayRect.right = (int) (getDensityDependentValue(menuBoundsObject.getInt("right"), getContext()) * scale);
displayRect.bottom = (int) (getDensityDependentValue(menuBoundsObject.getInt("bottom") + 25, getContext()) * scale);
if(!this.isInSelectionMode()){
this.startSelectionMode();
}
// This will send the menu rect
this.showContextMenu(displayRect);
drawSelectionHandles();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Receives the content width for the page.
*/
public void tsjiSetContentWidth(float contentWidth){
this.contentWidth = (int) this.getDensityDependentValue(contentWidth, ctx);
}
//*****************************************************
//*
//* Density Conversion
//*
//*****************************************************
/**
* Returns the density dependent value of the given float
* #param val
* #param ctx
* #return
*/
public float getDensityDependentValue(float val, Context ctx){
// Get display from context
Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
// Calculate min bound based on metrics
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val * (metrics.densityDpi / 160f);
}
/**
* Returns the density independent value of the given float
* #param val
* #param ctx
* #return
*/
public float getDensityIndependentValue(float val, Context ctx){
// Get display from context
Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
// Calculate min bound based on metrics
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val / (metrics.densityDpi / 160f);
}
}
Thanks In Advance!
I realize this is an old question, but there is not an accepted answer, and none of the provided answers have many votes.
I have achieved what you are trying to accomplish by creating a custom action mode callback. This allows the creation of a custom contextual action bar (CAB) when (in the case of a WebView) a user long-clicks the view. NOTE: This only works in 4.0+ and one piece only works in 4.4.
Create a new Android XML file containing the layout for your custom menu. Here is mine (context_menu.xml) as an example:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/copy"
android:icon="#drawable/ic_action_copy"
android:showAsAction="always" <!-- Forces this button to be shown -->
android:title="#string/copy">
</item>
<item
android:id="#+id/button2"
android:icon="#drawable/menu_button2icon"
android:showAsAction="ifRoom" <!-- Show if there is room on the screen-->
android:title="#string/button2">
</item>
<!-- Add as many more items as you want.
Note that if you use "ifRoom", an overflow menu will populate
to hold the action items that did not fit in the action bar. -->
</menu>
Now that the menu is defined, create a callback for it:
public class CustomWebView extends WebView {
private ActionMode.Callback mActionModeCallback;
private class CustomActionModeCallback implements ActionMode.Callback {
// Called when the action mode is created; startActionMode() was called
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
// Called each time the action mode is shown.
// Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Note: This is called every time the selection handlebars move.
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.copy:
// Do some stuff for this button
mode.finish(); // Action picked, so close the CAB
return true;
case R.id.button2:
// Do some stuff for this button
mode.finish();
return true;
// Create a case for every item
...
default:
mode.finish();
return false;
}
}
// Called when the user exits the action mode
#Override
public void onDestroyActionMode(ActionMode mode) {
// This will clear the selection highlight and handlebars.
// However, it only works in 4.4; I am still investigating
// how to reliably clear the selection in 4.3 and earlier
clearFocus();
}
}
Once those pieces are in place, override the startActionMode method so that the callback will actually occur on a long-click:
public class CustomWebView extends WebView {
#Override
public ActionMode startActionMode(Callback callback) {
ViewParent parent = getParent();
if (parent == null) {
return null;
}
mActionModeCallback = new CustomActionModeCallback();
return parent.startActionModeForChild(this, mActionModeCallback);
}
}
Now you have a custom menu that will be appear and populate when a user long-clicks your WebView. For reference, I found this information from an Android Developer tutorial and modified the suggestion from this answer.
One final note: This technique overrides the native copy/paste menu for selected text. If you want to maintain that functionality, you will need to implement it yourself. (That is why my first button is 'copy'.) If you need that, refer to this Google tutorial for more information and the proper way to implement it.
Use could the setOnTouchListener() for long/short long press. and return true so that nothing happens thereby overriding the default text selection feature.
I have been able to resolve this. I was also facing this issue and could not find any solution on the web.
So, if you set up a LongClick listener, the Webview would stop showing selection at all. After delving deep into the Webview code, I found that it was calling WebView's method startRunMode and passing an instance of SelectActionCallbackMode class.
I simply extended the Webview class and overrided the startRunMode method like this:
public ActionMode startActionMode(ActionMode.Callback callback)
{
actionModeCallback = new CustomizedSelectActionModeCallback();
return super.startActionMode(actionModeCallback);
}
This forced the Webview to display my Callback instead of displaying Webview's default one. This ensured that selection worked as smoothly as before and my CAB was displayed each time selection was made. Only caveat was that I had to write code to dismiss the CAB myself.
Tested on 4.1, 4.2 and 4.3 devices.
Hope this helps.
I could suggest a workaround for this.
You could use setOnTouchListener and then implement the long press yourself.
onTouch --
case MotionEvent.ACTION_DOWN:
mHanlder.removeCallbacks(startActionBar);
mHandler.postDelayed(startActionBar,1000);
This way you could achieve the same aciton.
some reason the KeyEvent down & up doesn't work in API LEVEL 16+ (Android 4.1+ JELLY_BEAN). It doesn't fire the WebView's loadUrl. So I had to replace the dispatchKeyEvent with dispatchTouchEvent. Here's the code:
...
MotionEvent touchDown = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, touchX, touchY, 0);
webView.dispatchTouchEvent(touchDown);
touchDown.recycle();
MotionEvent touchUp = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, touchX, touchY, 0);
webView.dispatchTouchEvent(touchUp);
touchUp.recycle();
String url = mUrl;
...
try it..
For disabling your text selection from webview try this,
webView.setWebChromeClient(new WebChromeClient(){
[.... other overrides....]
// #Override
// https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
// If you DO NOT want to start selection by long click,
// the remove this function
// (All this is undocumented stuff...)
public void onSelectionStart(WebView view) {
// By default we cancel the selection again, thus disabling
// text selection unless the chrome client supports it.
// view.notifySelectDialogDismissed();
}
});
You might also try overriding View.performLongClick(), which is responsible for calling View.onLongPress(). You could also try going up to the parent View's long press events. Or all the way up to the Window.Callback for your activity (via Activity.getWindow().get/setCallback()).
I'm wondering whether there are other ways for the standard selection context menu to appear besides the long-press event, for example maybe with a trackball or hardware keyboard.
in case someone is trying to simply remove the default text selection, I had the same issue on a Samsung Galaxy Tab on Android 4.1.2 and ended up writing my own WebView:
public class CustomWebView extends WebView {
public CustomWebView(Context context) {
super(context);
this.setUp(context);
}
public CustomWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setUp(context);
}
public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setUp(context);
}
private void setUp(Context context) {
this.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
return true;
}
});
this.setLongClickable(false);
}
#Override
public boolean performLongClick() {
return true;
}
}
and referring it in my xml:
<com...CustomWebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Use setOnTouchListener() implement long press
I started working on AndEngine and I have learnt a lot but i am stuck in creating levels selector for my game. I know this is something to do with getTextureManager() as i went through this when i am trying to attach ITextureRegion to my sprite object, that i solved but this time i am going nowhere for last day.
mainActivity.java
import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.WakeLockOptions;
import org.andengine.engine.options.resolutionpolicy.FillResolutionPolicy;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.Background;
import org.andengine.opengl.font.FontFactory;
import org.andengine.opengl.texture.ITexture;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.andengine.opengl.texture.atlas.bitmap.BuildableBitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.source.IBitmapTextureAtlasSource;
import org.andengine.opengl.texture.atlas.buildable.builder.BlackPawnTextureAtlasBuilder;
import org.andengine.opengl.texture.atlas.buildable.builder.ITextureAtlasBuilder.TextureAtlasBuilderException;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegion;
import org.andengine.ui.activity.SimpleBaseGameActivity;
import org.andengine.util.color.Color;
import android.graphics.Typeface;
import android.util.Log;
public class Main_Activity extends SimpleBaseGameActivity {
EngineOptions engine;
Camera mCamera;
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
LevelSelector ls;
Scene mScene;
public org.andengine.opengl.font.Font mFont;
ITextureRegion mTextureRegion;
#Override
public EngineOptions onCreateEngineOptions() {
mCamera = new Camera(0, 0, WIDTH, HEIGHT);
engine = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);
engine.setWakeLockOptions(WakeLockOptions.SCREEN_ON);
return engine;
}
#Override
protected void onCreateResources() {
BuildableBitmapTextureAtlas mBitmapTextureAtlas = new BuildableBitmapTextureAtlas(this.getTextureManager() ,32 , 32);
mTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "marble.png");
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
this.mFont = FontFactory.create(this.getFontManager(), this.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32);
this.mFont.load();
}
#Override
protected Scene onCreateScene() {
mScene = new Scene();
mScene.setBackground(new Background(0.09804f, 0.6274f, 0.8784f));
ls = new LevelSelector(5, 2, WIDTH, HEIGHT, mScene, mEngine);
ls.createTiles(mTextureRegion, mFont);
ls.show();
return mScene;
}
}
LayerSelector.java
import org.andengine.engine.options.EngineOptions;
import org.andengine.entity.Entity;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.sprite.Sprite;
import org.andengine.entity.text.Text;
import org.andengine.input.touch.TouchEvent;
import org.andengine.opengl.font.Font;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegion;
import android.service.wallpaper.WallpaperService.Engine;
public class LevelSelector extends Entity {
/* Level selector layer properties */
private final int COLUMNS = 6;
private final int ROWS = 6;
/* Level selector tile properties */
private final int TILE_DIMENSION = 50;
private final int TILE_PADDING = 15;
private final Scene mScene;
private final org.andengine.engine.Engine mEngine;
/*
* The mChapter variable can allow each LevelSelector object to contain
* level tiles which begin levels in different chapters.
*/
private final int mChapter;
/* Variable containing the current max level unlocked */
private final int mMaxLevel;
/* Camera width and height are needed for the layout */
private final int mCameraWidth;
private final int mCameraHeight;
/* Initial x/y coordinates used for tile positioning */
private final float mInitialX;
private final float mInitialY;
/*
* Variable which defines whether the LevelSelector is hidden or visible
*/
private boolean mHidden = true;
/**
* The LevelSelector object can be used to display a grid of level tiles for
* user selection.
*
* #param pMaxLevel
* Current max unlocked level.
* #param pChapter
* Chapter/world number of this particular LevelSelector.
* #param pCameraWidth
* Camera object's width value.
* #param pCameraHeight
* Camera object's height value.
* #param pScene
* The Scene in which the LevelSelector will be displayed on.
* #param pEngine
* AndEngine's mEngine object.
*/
public LevelSelector(final int pMaxLevel, final int pChapter,
final int pCameraWidth, final int pCameraHeight,
final Scene pScene, final org.andengine.engine.Engine pEngine) {
/* Initialize member variables */
this.mScene = pScene;
this.mEngine = pEngine;
this.mChapter = pChapter;
this.mMaxLevel = pMaxLevel;
this.mCameraWidth = pCameraWidth;
this.mCameraHeight = pCameraHeight;
/*
* Obtain the initial tile's X coordinate by subtracting half of the
* entire level selector width including all tiles and padding from the
* center of the Scene
*/
final float halfLevelSelectorWidth = ((TILE_DIMENSION * COLUMNS) + TILE_PADDING
* (COLUMNS - 1)) * 0.5f;
this.mInitialX = (this.mCameraWidth * 0.5f) - halfLevelSelectorWidth;
/* Same math as above applies to the Y coordinate */
final float halfLevelSelectorHeight = ((TILE_DIMENSION * ROWS) + TILE_PADDING
* (ROWS - 1)) * 0.5f;
this.mInitialY = (this.mCameraHeight * 0.5f) + halfLevelSelectorHeight;
mEngine.getTextureManager();
}
/**
* Create the level tiles with a customized ITextureRegion representation as
* well as a customized Font.
*
* #param pTextureRegion
* The ITextureRegion to supply each of the level tiles.
* #param pFont
* The Font to be displayed by Text written on the tiles,
* specifying tile level number for example.
*/
public void createTiles(final ITextureRegion pTextureRegion,
final org.andengine.opengl.font.Font pFont) {
/* Temp coordinates for placing level tiles */
float tempX = this.mInitialX + TILE_DIMENSION * 0.5f;
float tempY = this.mInitialY - TILE_DIMENSION * 0.5f;
/* Current level of the tile to be placed */
int currentTileLevel = 1;
/*
* Loop through the Rows, adjusting tempY coordinate after each
* iteration
*/
for (int i = 0; i < ROWS; i++) {
/*
* Loop through the column positions, placing a LevelTile in each
* column
*/
for (int o = 0; o < COLUMNS; o++) {
final boolean locked;
/* Determine whether the current tile is locked or not */
if (currentTileLevel <= mMaxLevel) {
locked = false;
} else {
locked = true;
}
/* Create a level tile */
LevelTile levelTile = new LevelTile(tempX, tempY, locked,
currentTileLevel, pTextureRegion, pFont);
/*
* Attach the level tile's text based on the locked and
* currentTileLevel variables pass to its constructor
*/
levelTile.attachText();
/* Register & Attach the levelTile object to the LevelSelector */
mScene.registerTouchArea(levelTile);
this.attachChild(levelTile);
/* Increment the tempX coordinate to the next column */
tempX = tempX + TILE_DIMENSION + TILE_PADDING;
/* Increment the level tile count */
currentTileLevel++;
}
/* Reposition the tempX coordinate back to the first row (far left) */
tempX = mInitialX + TILE_DIMENSION * 0.5f;
/* Reposition the tempY coordinate for the next row to apply tiles */
tempY = tempY - TILE_DIMENSION - TILE_PADDING;
}
}
/**
* Display the LevelSelector on the Scene.
*/
public void show() {
/* Register as non-hidden, allowing touch events */
mHidden = false;
/* Attach the LevelSelector the the Scene if it currently has no parent */
if (!this.hasParent()) {
mScene.attachChild(this);
}
/* Set the LevelSelector to visible */
this.setVisible(true);
}
/**
* Hide the LevelSelector on the Scene.
*/
public void hide() {
/* Register as hidden, disallowing touch events */
mHidden = true;
/* Remove the LevelSelector from view */
this.setVisible(false);
}
public class LevelTile extends Sprite {
/*
* The LevelTile should keep track of level number and lock status. Feel
* free to add additional data within level tiles
*/
private final boolean mIsLocked;
private final int mLevelNumber;
private final org.andengine.opengl.font.Font mFont;
private Text mTileText;
/*
* Each level tile will be sized according to the constant
* TILE_DIMENSION within the LevelSelector class
*/
public LevelTile(float pX, float pY, boolean pIsLocked,
int pLevelNumber, ITextureRegion pTextureRegion, org.andengine.opengl.font.Font pFont) {
super(pX, pY, LevelSelector.this.TILE_DIMENSION,
LevelSelector.this.TILE_DIMENSION, pTextureRegion,
LevelSelector.this.mEngine.getVertexBufferObjectManager());
/* Initialize the necessary variables for the LevelTile */
this.mFont = pFont;
this.mIsLocked = pIsLocked;
this.mLevelNumber = pLevelNumber;
}
/* Method used to obtain whether or not this level tile represents a
* level which is currently locked */
public boolean isLocked() {
return this.mIsLocked;
}
/* Method used to obtain this specific level tiles level number */
public int getLevelNumber() {
return this.mLevelNumber;
}
/*
* Attach the LevelTile's text to itself based on whether it's locked or
* not. If not, then the level number will be displayed on the level
* tile.
*/
public void attachText() {
String tileTextString = null;
/* If the tile's text is currently null... */
if (this.mTileText == null) {
/*
* Determine the tile's string based on whether it's locked or
* not
*/
if (this.mIsLocked) {
tileTextString = "Locked";
} else {
tileTextString = String.valueOf(this.mLevelNumber);
}
/* Setup the text position to be placed in the center of the tile */
final float textPositionX = LevelSelector.this.TILE_DIMENSION * 0.5f;
final float textPositionY = textPositionX;
/* Create the tile's text in the center of the tile */
this.mTileText = new Text( textPositionX,
textPositionY, this.mFont,
tileTextString, tileTextString.length(),
LevelSelector.this.mEngine
.getVertexBufferObjectManager());
/* Attach the Text to the LevelTile */
this.attachChild(mTileText);
}
}
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
float pTouchAreaLocalX, float pTouchAreaLocalY) {
/* If the LevelSelector is not hidden, proceed to execute the touch
* event */
if (!LevelSelector.this.mHidden) {
/* If a level tile is initially pressed down on */
if (pSceneTouchEvent.isActionDown()) {
/* If this level tile is locked... */
if (this.mIsLocked) {
/* Tile Locked event... */
LevelSelector.this.mScene.getBackground().setColor(
org.andengine.util.color.Color.RED);
} else {
/* Tile unlocked event... This event would likely prompt
* level loading but without getting too complicated we
* will simply set the Scene's background color to green */
LevelSelector.this.mScene.getBackground().setColor(
org.andengine.util.color.Color.GREEN);
/**
* Example level loading:
* LevelSelector.this.hide();
* SceneManager.loadLevel(this.mLevelNumber);
*/
}
return true;
}
}
return super.onAreaTouched(pSceneTouchEvent, pTouchAreaLocalX,
pTouchAreaLocalY);
}
}
}
FYI, this code belongs to code bundle in Android game development using AndEngine cookbook.
I have edited few variables like font and engine , but i have't changed the logic or flow of program.
Now when I run this I get the below screen
Alright i solved my problem, its was the padding i was using, it should't be there.
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
it should be
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 0, 0));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
I'm currently facing an issue with my android game. Normally when calling SoundPool.play() the function needs about 0.003 seconds to finish, but sometimes it takes 0.2 seconds which makes my game stutter. where could his anomaly come from?
thanks to Tim, using a Thread for playing seemed to workaround the problem successfully.
Thread
package org.racenet.racesow.threads;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.racenet.racesow.models.SoundItem;
import android.media.SoundPool;
/**
* Thread for playing sounds
*
* #author soh#zolex
*
*/
public class SoundThread extends Thread {
private SoundPool soundPool;
public BlockingQueue<SoundItem> sounds = new LinkedBlockingQueue<SoundItem>();
public boolean stop = false;
/**
* Constructor
*
* #param soundPool
*/
public SoundThread(SoundPool soundPool) {
this.soundPool = soundPool;
}
/**
* Dispose a sound
*
* #param soundID
*/
public void unloadSound(int soundID) {
this.soundPool.unload(soundID);
}
#Override
/**
* Wait for sounds to play
*/
public void run() {
try {
SoundItem item;
while (!this.stop) {
item = this.sounds.take();
if (item.stop) {
this.stop = true;
break;
}
this.soundPool.play(item.soundID, item.volume, item.volume, 0, 0, 1);
}
} catch (InterruptedException e) {}
}
}
SoundItem
package org.racenet.racesow.models;
/**
* SoundItem will be passed to the SoundThread which
* will handle the playing of sounds
*
* #author soh#zolex
*
*/
public class SoundItem {
public int soundID;
public float volume;
public boolean stop = false;
/**
* Default constructor
*
* #param soundID
* #param volume
*/
public SoundItem(int soundID, float volume) {
this.soundID = soundID;
this.volume = volume;
}
/**
* Constructor for the item
* which will kill the thread
*
* #param stop
*/
public SoundItem(boolean stop) {
this.stop = stop;
}
}
I am trying to do the following from a live wallpaper tutorial I found here.
/**
* Do the actual drawing stuff
*/
private void doDraw(Canvas canvas) {
Bitmap b = BitmapFactory.decodeResource(context.getResources(), IMAGES[current]);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(b, 0, 0, null);
Log.d(TAG, "Drawing finished.");
}
/**
* Update the animation, sprites or whatever.
* If there is nothing to animate set the wait
* attribute of the thread to true
*/
private void updatePhysics() {
// if nothing was updated :
// this.wait = true;
if(previousTime - System.currentTimeMillis() >= 41) { //24 FPS
current = current < IMAGES.length ? current++ : 0;
}
Log.d(TAG, "Updated physics.");
}
But it doesn't seem to work. What am I doing wrong. The "Drawing finished." and "Updated physics." messages are getting printed. But I see the first image only. I'm testing it on the emulator.
Any help would be appreciated. Thanks
I have worked out a simple sample live wallpaper where the color shift over time. Maybe you can use this as a starting point:
package com.cmwmobile.android.samples;
import android.graphics.Canvas;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
/**
* The SampleLiveWallpaperService class is responsible for showing the
* animation and is an interface to android.
* #author Casper Wakkers - www.cmwmobile.com
*/
public class SampleLiveWallpaperService extends WallpaperService {
private Handler handler = null;
/**
* Inner class representing the actual implementation of the
* Live Wallpaper {#link Engine}.
*/
private class SampleLiveWallpaperEngine extends Engine {
private boolean visible = false;
private int[] colors = {0, 0, 0} ;
/**
* Runnable implementation for the actual work.
*/
private final Runnable runnableSomething = new Runnable() {
/**
* {#inheritDoc}
*/
public void run() {
drawSomething();
}
};
/**
* The drawSomething method is responsible for drawing the animation.
*/
private void drawSomething() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawARGB(200, colors[0], colors[1], colors[2]);
}
updateColors(colors);
}
finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
// Reschedule the next redraw.
handler.removeCallbacks(runnableSomething);
if (visible) {
// Play around with the delay for an optimal result.
handler.postDelayed(runnableSomething, 25);
}
}
/**
* Method updateColors updates the colors by increasing the value
* per RGB. The values are reset to zero if the maximum value is
* reached.
* #param colors to be updated.
*/
private void updateColors(int[] colors) {
if (colors[0] < 255) {
colors[0]++;
}
else {
if (colors[1] < 255) {
colors[1]++;
}
else {
if (colors[2] < 255) {
colors[2]++;
}
else {
colors[0] = 0;
colors[1] = 0;
colors[2] = 0;
}
}
}
}
/**
* {#inheritDoc}
*/
public void onDestroy() {
super.onDestroy();
handler.removeCallbacks(runnableSomething);
}
/**
* {#inheritDoc}
*/
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
this.visible = visible;
if (visible) {
drawSomething();
}
else {
handler.removeCallbacks(runnableSomething);
}
}
}
/**
* Constructor. Creates the {#link Handler}.
*/
public SampleLiveWallpaperService() {
handler = new Handler();
}
/**
* {#inheritDoc}
*/
public Engine onCreateEngine() {
return new SampleLiveWallpaperEngine();
}
}
I created a quick tutorial on my employers blog about using SVG in a Live Wallpaper, check it out if you like.
Part 1 http://blog.infrared5.com/2012/03/android-live-wallpaper/
Part 2 http://blog.infrared5.com/2012/03/android-live-wallpaper-part-2/