Recently I'm making a game which requires continuous animation.
I'll explain it further.
I got view let's say top of the screen and I need to animate it from the top of the screen to the bottom of the screen and then it will go back to the original spot (Top of the screen) when the view is on the bottom.
Now there's 6 views which does the exactly same thing.
Animate from top of the screen to the bottom of the screen and back when they are on the bottom.
But when they are already moving there's really annoying stuttering and lags on the application.
Here's the code :
#Override
public void onWindowFocusChanged(boolean focus) {
new LongOperation().execute();
}
private class LongOperation extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
play.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (!failedM) {
running = true;
soundPool.play(click_sound, 1, 1, 0, 0, 1);
play.setVisibility(View.INVISIBLE);
p1.setVisibility(View.VISIBLE);
p2.setVisibility(View.VISIBLE);
p3.setVisibility(View.VISIBLE);
c1.setVisibility(View.VISIBLE);
c2.setVisibility(View.VISIBLE);
c3.setVisibility(View.VISIBLE);
final Handler mHandler = new Handler();
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(60);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mHandler.post(new Runnable() {
#Override
public void run() {
p1.getHitRect(rect1);
p2.getHitRect(rect2);
p3.getHitRect(rect3);
c1.getHitRect(rect4);
c2.getHitRect(rect5);
c3.getHitRect(rect6);
if (Rect.intersects(rect, rect1)
{
.....
}
if (Rect.intersects(rect, rect2)
) {
.....
}
if (Rect.intersects(rect, rect3)
) {
.....
}
if (Rect.intersects(rect, rect4)
) {
.....
}
if (Rect.intersects(rect, rect5)
) {
.....
}
if (Rect.intersects(rect, rect6)
) {
.....
}
downy1 += 1;
downy2 += 1;
downy3 += 1;
downy4 += 1;
downy5 += 1;
downy6 += 1;
params1.topMargin = params1.topMargin
+ downy1;
params2.topMargin = params2.topMargin
+ downy2;
params3.topMargin = params3.topMargin
+ downy3;
params4.topMargin = params4.topMargin
+ downy4;
params5.topMargin = params5.topMargin
+ downy5;
params6.topMargin = params6.topMargin
+ downy6;
p1.setLayoutParams(params1);
p2.setLayoutParams(params2);
p3.setLayoutParams(params3);
c1.setLayoutParams(params4);
c2.setLayoutParams(params5);
c3.setLayoutParams(params6);
if (p1.getTop() > height) {
downy1 = 0;
params1.leftMargin = newX;
params1.topMargin = orig1y;
p1.setLayoutParams(params1);
}
if (p2.getTop() > height) {
downy2 = 0;
params2.leftMargin = newX;
params2.topMargin = orig2y;
p2.setLayoutParams(params2);
}
if (p3.getTop() > height) {
downy3 = 0;
params3.leftMargin = newX;
params3.topMargin = orig3y;
p3.setLayoutParams(params3);
}
if (c1.getTop() > height
&& !failedM) {
downy4 = 0;
params4.leftMargin = newX;
params4.topMargin = orig4y;
c1.setLayoutParams(params4);
}
if (c2.getTop() > height
&& !failedM) {
downy5 = 0;
params5.leftMargin = newX;
params5.topMargin = orig5y;
c2.setLayoutParams(params5);
}
if (c3.getTop() > height
&& !failedM) {
downy6 = 0;
params6.leftMargin = newX;
params6.topMargin = orig6y;
c3.setLayoutParams(params6);
}
}
});
}
}
}).start();
}
});
return "Executed";
}
#Override
protected void onPostExecute(String result) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
As you can see there's (if view.getTop() > height)
What it means is that when the view Y is bigger than the height which is when the view is moving from the top to the bottom it will go back to the top.
I hope I made it clear
Thanks in advance.
You should use the animation framework. (view.animate()...).
Setting layout params requires re-calculating the layout, which is potentially not a cheap operation (depends on your layout).
You can use animation framework or set translateX/Y etc properties of the view, which avoid re-calculating the layout.
Related
I'm trying to create a small service that connects to a site does the operations and during the process updates the textview present. Nothing wrong until I enter a for loop, because the activity stops, it becomes completely white until when it does not end.
I would like to update the text step by step.
I created an async task, but it gives me the error '
failed to load window.
Below is my code, I state that I put all the permissions.
public class GameTask extends AsyncTask<String, Integer, Long> {
private ProgressBar progressBar;
private TextView text;
public GameTask(ProgressBar progressBar, TextView text) {
this.progressBar = progressBar;
this.text = text;
}
#Override
protected Long doInBackground(String... strings) {
startGameThread(progressBar, text);
return null;
}
private void startGameThread (final ProgressBar progressBar, final TextView text) {
final Handler mHandler = new Handler();
final Runnable mRunnable = new Runnable() {
#Override
public void run() {
try {
progressBar.setProgress(0);
text.setText("Thread Iniziato...");
for (int i = 0; i <= 1000; i++) {
if (i == 0 || i % 10 == 0) {
text.setText("NUMBER " + i);
}
}
progressBar.setProgress(100);
} catch (IOException e) {
e.printStackTrace();
}
}
};
new Thread(mRunnable).start();
}
}
Try this method into AsyncTask
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
try {
progressBar.setProgress(0);
text.setText("Thread Iniziato...");
for (int i = 0; i <= 1000; i++) {
if (i == 0 || i % 10 == 0) {
text.setText("NUMBER " + i);
}
}
progressBar.setProgress(100);
} catch (IOException e) {
e.printStackTrace();
}
}
You can take full advantage of AsyncTask to perform tasks in background and UI thread without a Handler:
public class GameTask extends AsyncTask<String, Integer, Long> {
private ProgressBar progressBar;
private TextView text;
public GameTask(ProgressBar progressBar, TextView text) {
this.progressBar = progressBar;
this.text = text;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
// UI thread
text.setText("Thread Iniziato...");
}
#Override
protected Long doInBackground(String... strings) {
// Non-UI thread
for (int i = 0; i <= 1000; i++) {
if (i == 0 || i % 10 == 0) {
publishProgress(i/100, i);
} else {
publishProgress(i/100);
}
try {
Thread.sleep(100); // Backpressure if needed for your sample
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// UI thread
progressBar.setProgress(values[0]);
if (values.length > 1) {
text.setText("NUMBER " + values[1]);
}
}
}
As this Process is Running Inside AsyncTask, You need to Use runOnUiThread method to update the TextView. UI can only be Upated on MainThread.
private void startGameThread () {
// final Handler mHandler = new Handler();
final Runnable mRunnable = new Runnable() {
#Override
public void run() {
for (int i = 0; i <= 1000; i++) {
if (i == 0 || i % 10 == 0) {
final int finalI = i;
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(String.valueOf(finalI));
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
new Thread(mRunnable).start();
}
I am fairly new to coding and app development(almost a month) so please bear with me. I am working on an app that uses a Barcode scanning plugin (customized as per needs).
In the app's main view (first window) I have a block, which on click triggers the Barcode plugin's scan(). This opens a scanner of the exact dimensions as the block of the main view. But if I rotate the device, the Barcode scanner view's dimensions goes haywire.
Is there a way I can adjust/change the x,y,w,h values of the Barcode scanner view so to align it with my app's block in the main view?
Upon clicking the Scan block on my app's main view, this scanner is opened as an overlay with custom buttons:
Upon rotating the device and clicking the scan block (green block on app's main view), this is how it looks:
Angular code passing the dimensions for the green scan block on app's main view:
.directive('barcodeScanner', function ($localStorage, _, $window) {
function link(scope, element, attrs){
scope.scanning = false;
scope.paused = false;
//Barcode-Scanning Green Window
var width = $window.innerWidth;
var height = width * 3/4;
if(width > 450){
width = 400;
height = 300;
}
scope.dimensionStyle = {
width: width+"px",
height: height+"px",
"margin-left" : "auto",
"margin-right" : "auto"
}
scope.$on('stop-scanner', function () {
scope.paused=false;
stopScanner();
});
scope.$on('start-scanner', function () {
scope.paused=true;
startScanner();
});
var mH = $window.innerHeight,
mW = $window.innerWidth;
var startBtn = element[0].querySelector('.barcode-start-btn');
function startScanner(){
var rect = startBtn.getBoundingClientRect();
var options = {
wPer : rect.width/mW,
hPer : rect.height/mH,
yPer : rect.top/mH,
xPer : rect.left/mW
}
scope.scanning = true;
cordova.plugins.barcodescanner.scan(function(result) {
scope.$emit('barcode-scanned',result);
},
options
);
}
My barcodescanner.java of the plugin that is triggered to open the scanner:
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
this.requestArgs = args;
if (action.equals(SCAN)) {
// create fragment
if(!hasPermisssion()) {
requestPermissions(0);
} else {
scan(args);
}
} else if(action.equals(STOP)) {
stop(args);
} else {
return false;
}
return true;
}
public void scan(final JSONArray args) {
final CordovaPlugin that = this;
cordova.getThreadPool().execute(new Runnable() {
public void run() {
int maxHeight = webView.getView().getHeight();
int maxWidth = webView.getView().getWidth();
double xPer = 0/360;
double yPer = 82/568;
double hPer = 270/568;
double wPer = 360/360;
// add config as intent extras
if (args.length() > 0) {
JSONObject obj;
for (int i = 0; i < args.length(); i++) {
try {
obj = args.getJSONObject(i);
if(obj.has(X_PER)){
xPer = obj.getDouble(X_PER);
}
if(obj.has(Y_PER)){
yPer = obj.getDouble(Y_PER);
}
if(obj.has(H_PER)){
hPer = obj.getDouble(H_PER);
}
if(obj.has(W_PER)){
wPer = obj.getDouble(W_PER);
}
} catch (JSONException e) {
Log.i("CordovaLog", e.getLocalizedMessage());
continue;
}
}
}
Bundle bundle = new Bundle();
bundle.putDouble("x", maxWidth*xPer);
bundle.putDouble("y", maxHeight*yPer);
bundle.putDouble("w", maxWidth*wPer);
bundle.putDouble("h", maxHeight*hPer);
openCameraPopup(bundle);
}
});
}
My fragment class that implements ZxingScannerView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if(state == null){
state = this.getArguments();
}
if(state != null) {
x = state.getDouble("x");
y = state.getDouble("y");
w = state.getDouble("w");
h = state.getDouble("h");
}
mScannerView = new ZXingScannerView(getActivity()){
#Override
public void setupCameraPreview(CameraWrapper cameraWrapper) {
super.setupCameraPreview(cameraWrapper);
buttonStop = new Button(getActivity());
buttonStop.setText("STOP");
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
this.addView(buttonStop);
buttonStop.setOnClickListener( new OnClickListener() {
#Override
public void onClick(View v) {
mScannerView.stopCamera();
mScannerView.removeAllViews();
}
});
}
};
mScannerView.setZ(10);
mScannerView.setAutoFocus(true);
mScannerView.setFlash(false);
mScannerView.setX((float) x);
mScannerView.setY((float) y);
mScannerView.setLayoutParams(new ViewGroup.LayoutParams((int)w, (int)h));
return mScannerView;
}
I suggest you to lock your position using screen orientation landscape. So it will change position everytime you rotate your mobile.
I looked several places, including on here and the answers I found did not help. I also looked through the code of the template that I am using and did not see where I went wrong, so I am sorry if this seems like a bad question. But I am making a watch face and I am trying to extend it from my Engine, WeatherWatchFaceEngine. But when I do this, I get the error that is in the title of this question. What have I done wrong?
This is the code of the watch face:
public class NimbusSplashAnalog extends WeatherWatchFaceService {
/**
* Update rate in milliseconds for interactive mode. We update once a second to advance the
* second hand.
*/
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
/**
* Handler message id for updating the time periodically in interactive mode.
*/
private static final int MSG_UPDATE_TIME = 0;
#Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends WeatherWatchFaceEngine {
Paint mBackgroundPaint;
Paint mHandPaint;
boolean mAmbient;
Time mTime;
final Handler mUpdateTimeHandler = new EngineHandler(this);
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
mTime.clear(intent.getStringExtra("time-zone"));
mTime.setToNow();
}
};
boolean mRegisteredTimeZoneReceiver = false;
/**
* Whether the display supports fewer bits for each color in ambient mode. When true, we
* disable anti-aliasing in ambient mode.
*/
boolean mLowBitAmbient;
#Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(NimbusSplashAnalog.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
Resources resources = NimbusSplashAnalog.this.getResources();
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(resources.getColor(R.color.analog_background));
mHandPaint = new Paint();
mHandPaint.setColor(resources.getColor(R.color.analog_hands));
mHandPaint.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
mHandPaint.setAntiAlias(true);
mHandPaint.setStrokeCap(Paint.Cap.ROUND);
mTime = new Time();
}
#Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
#Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
}
#Override
public void onTimeTick() {
super.onTimeTick();
invalidate();
}
#Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (mAmbient != inAmbientMode) {
mAmbient = inAmbientMode;
if (mLowBitAmbient) {
mHandPaint.setAntiAlias(!inAmbientMode);
}
invalidate();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
#Override
public void onDraw(Canvas canvas, Rect bounds) {
mTime.setToNow();
int width = bounds.width();
int height = bounds.height();
// Draw the background.
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
// Find the center. Ignore the window insets so that, on round watches with a
// "chin", the watch face is centered on the entire screen, not just the usable
// portion.
float centerX = width / 2f;
float centerY = height / 2f;
float secRot = mTime.second / 30f * (float) Math.PI;
int minutes = mTime.minute;
float minRot = minutes / 30f * (float) Math.PI;
float hrRot = ((mTime.hour + (minutes / 60f)) / 6f) * (float) Math.PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
float hrLength = centerX - 80;
if (!mAmbient) {
float secX = (float) Math.sin(secRot) * secLength;
float secY = (float) -Math.cos(secRot) * secLength;
canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaint);
}
float minX = (float) Math.sin(minRot) * minLength;
float minY = (float) -Math.cos(minRot) * minLength;
canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mHandPaint);
float hrX = (float) Math.sin(hrRot) * hrLength;
float hrY = (float) -Math.cos(hrRot) * hrLength;
canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHandPaint);
}
#Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mTime.clear(TimeZone.getDefault().getID());
mTime.setToNow();
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
NimbusSplashAnalog.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
NimbusSplashAnalog.this.unregisterReceiver(mTimeZoneReceiver);
}
/**
* Starts the {#link #mUpdateTimeHandler} timer if it should be running and isn't currently
* or stops it if it shouldn't be running but currently is.
*/
private void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
/**
* Returns whether the {#link #mUpdateTimeHandler} timer should be running. The timer should
* only run when we're visible and in interactive mode.
*/
private boolean shouldTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
/**
* Handle updating the time periodically in interactive mode.
*/
private void handleUpdateTimeMessage() {
invalidate();
if (shouldTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = INTERACTIVE_UPDATE_RATE_MS
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
}
}
}
private static class EngineHandler extends Handler {
private final WeakReference<NimbusSplashAnalog.Engine> mWeakReference;
public EngineHandler(NimbusSplashAnalog.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
#Override
public void handleMessage(Message msg) {
NimbusSplashAnalog.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
}
This is the code of the Engine/Service that I am extending from:
public abstract class WeatherWatchFaceService extends CanvasWatchFaceService {
public class WeatherWatchFaceEngine extends CanvasWatchFaceService.Engine
implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
DataApi.DataListener, NodeApi.NodeListener {
protected static final int MSG_UPDATE_TIME = 0;
protected long UPDATE_RATE_MS;
protected static final long WEATHER_INFO_TIME_OUT = DateUtils.HOUR_IN_MILLIS * 6;
protected final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Time zone changed
mWeatherInfoReceivedTime = 0;
mTime.clear(intent.getStringExtra("time-zone"));
mTime.setToNow();
}
};
/**
* Handler to update the time periodically in interactive mode.
*/
protected final Handler mUpdateTimeHandler = new Handler() {
#Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_UPDATE_TIME:
invalidate();
if (shouldUpdateTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = UPDATE_RATE_MS - (timeMs % UPDATE_RATE_MS);
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
requireWeatherInfo();
}
break;
}
}
};
protected int mTheme = 3;
protected int mTimeUnit = ConverterUtil.TIME_UNIT_12;
protected AssetManager mAsserts;
protected Bitmap mWeatherConditionDrawable;
protected GoogleApiClient mGoogleApiClient;
protected Paint mBackgroundPaint;
protected Paint mDatePaint;
protected Paint mDateSuffixPaint;
protected Paint mDebugInfoPaint;
protected Paint mTemperatureBorderPaint;
protected Paint mTemperaturePaint;
protected Paint mTemperatureSuffixPaint;
protected Paint mTimePaint;
protected Resources mResources;
protected String mWeatherCondition;
protected String mWeatherConditionResourceName;
protected Time mSunriseTime;
protected Time mSunsetTime;
protected Time mTime;
protected boolean isRound;
protected boolean mLowBitAmbient;
protected boolean mRegisteredService = false;
protected int mBackgroundColor;
protected int mBackgroundDefaultColor;
protected int mRequireInterval;
protected int mTemperature = Integer.MAX_VALUE;
protected int mTemperatureScale;
protected long mWeatherInfoReceivedTime;
protected long mWeatherInfoRequiredTime;
private String mName;
public WeatherWatchFaceEngine(String name) {
mName = name;
mGoogleApiClient = new GoogleApiClient.Builder(WeatherWatchFaceService.this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
}
#Override
public void onConnected(Bundle bundle) {
log("Connected: " + bundle);
getConfig();
Wearable.NodeApi.addListener(mGoogleApiClient, this);
Wearable.DataApi.addListener(mGoogleApiClient, this);
requireWeatherInfo();
}
#Override
public void onConnectionSuspended(int i) {
log("ConnectionSuspended: " + i);
}
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (int i = 0; i < dataEvents.getCount(); i++) {
DataEvent event = dataEvents.get(i);
DataMap dataMap = DataMap.fromByteArray(event.getDataItem().getData());
log("onDataChanged: " + dataMap);
fetchConfig(dataMap);
}
}
#Override
public void onPeerConnected(Node node) {
log("PeerConnected: " + node);
requireWeatherInfo();
}
#Override
public void onPeerDisconnected(Node node) {
log("PeerDisconnected: " + node);
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
log("ConnectionFailed: " + connectionResult);
}
#Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(WeatherWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
mResources = WeatherWatchFaceService.this.getResources();
mAsserts = WeatherWatchFaceService.this.getAssets();
mDebugInfoPaint = new Paint();
mDebugInfoPaint.setColor(Color.parseColor("White"));
mDebugInfoPaint.setTextSize(20);
mDebugInfoPaint.setAntiAlias(true);
mTime = new Time();
mSunriseTime = new Time();
mSunsetTime = new Time();
mRequireInterval = mResources.getInteger(R.integer.weather_default_require_interval);
mWeatherInfoRequiredTime = System.currentTimeMillis() - (DateUtils.SECOND_IN_MILLIS * 58);
mGoogleApiClient.connect();
}
#Override
public void onDestroy() {
log("Destroy");
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
#Override
public void onInterruptionFilterChanged(int interruptionFilter) {
super.onInterruptionFilterChanged(interruptionFilter);
log("onInterruptionFilterChanged: " + interruptionFilter);
}
#Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(WatchFaceService.PROPERTY_LOW_BIT_AMBIENT, false);
log("onPropertiesChanged: LowBitAmbient=" + mLowBitAmbient);
}
#Override
public void onTimeTick() {
super.onTimeTick();
log("TimeTick");
invalidate();
requireWeatherInfo();
}
#Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
log("onVisibilityChanged: " + visible);
if (visible) {
mGoogleApiClient.connect();
registerTimeZoneService();
// Update time zone in case it changed while we weren't visible.
mTime.clear(TimeZone.getDefault().getID());
mTime.setToNow();
} else {
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Wearable.DataApi.removeListener(mGoogleApiClient, this);
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
unregisterTimeZoneService();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
protected Paint createTextPaint(int color, Typeface typeface) {
Paint paint = new Paint();
paint.setColor(color);
if (typeface != null)
paint.setTypeface(typeface);
paint.setAntiAlias(true);
return paint;
}
protected boolean shouldUpdateTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
protected void fetchConfig(DataMap config) {
if (config.containsKey(Consts.KEY_WEATHER_UPDATE_TIME)) {
mWeatherInfoReceivedTime = config.getLong(Consts.KEY_WEATHER_UPDATE_TIME);
}
if (config.containsKey(Consts.KEY_WEATHER_CONDITION)) {
String cond = config.getString(Consts.KEY_WEATHER_CONDITION);
if (TextUtils.isEmpty(cond)) {
mWeatherCondition = null;
} else {
mWeatherCondition = cond;
}
}
if (config.containsKey(Consts.KEY_WEATHER_TEMPERATURE)) {
mTemperature = config.getInt(Consts.KEY_WEATHER_TEMPERATURE);
if (mTemperatureScale != ConverterUtil.FAHRENHEIT) {
mTemperature = ConverterUtil.convertFahrenheitToCelsius(mTemperature);
}
}
if (config.containsKey(Consts.KEY_WEATHER_SUNRISE)) {
mSunriseTime.set(config.getLong(Consts.KEY_WEATHER_SUNRISE) * 1000);
log("SunriseTime: " + mSunriseTime);
}
if (config.containsKey(Consts.KEY_WEATHER_SUNSET)) {
mSunsetTime.set(config.getLong(Consts.KEY_WEATHER_SUNSET) * 1000);
log("SunsetTime: " + mSunsetTime);
}
if (config.containsKey(Consts.KEY_CONFIG_TEMPERATURE_SCALE)) {
int scale = config.getInt(Consts.KEY_CONFIG_TEMPERATURE_SCALE);
if (scale != mTemperatureScale) {
if (scale == ConverterUtil.FAHRENHEIT) {
mTemperature = ConverterUtil.convertCelsiusToFahrenheit(mTemperature);
} else {
mTemperature = ConverterUtil.convertFahrenheitToCelsius(mTemperature);
}
}
mTemperatureScale = scale;
}
if (config.containsKey(Consts.KEY_CONFIG_THEME)) {
mTheme = config.getInt(Consts.KEY_CONFIG_THEME);
}
if (config.containsKey(Consts.KEY_CONFIG_TIME_UNIT)) {
mTimeUnit = config.getInt(Consts.KEY_CONFIG_TIME_UNIT);
}
if (config.containsKey(Consts.KEY_CONFIG_REQUIRE_INTERVAL)) {
mRequireInterval = config.getInt(Consts.KEY_CONFIG_REQUIRE_INTERVAL);
}
invalidate();
}
protected void getConfig() {
log("Start getting Config");
Wearable.NodeApi.getLocalNode(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
#Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
Uri uri = new Uri.Builder()
.scheme("wear")
.path(Consts.PATH_CONFIG + mName)
.authority(getLocalNodeResult.getNode().getId())
.build();
getConfig(uri);
uri = new Uri.Builder()
.scheme("wear")
.path(Consts.PATH_WEATHER_INFO)
.authority(getLocalNodeResult.getNode().getId())
.build();
getConfig(uri);
}
});
}
protected void getConfig(Uri uri) {
Wearable.DataApi.getDataItem(mGoogleApiClient, uri)
.setResultCallback(
new ResultCallback<DataApi.DataItemResult>() {
#Override
public void onResult(DataApi.DataItemResult dataItemResult) {
log("Finish Config: " + dataItemResult.getStatus());
if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {
fetchConfig(DataMapItem.fromDataItem(dataItemResult.getDataItem()).getDataMap());
}
}
}
);
}
protected void log(String message) {
Log.d(WeatherWatchFaceService.this.getClass().getSimpleName(), message);
}
protected void registerTimeZoneService() {
//TimeZone
if (mRegisteredService) {
return;
}
mRegisteredService = true;
// TimeZone
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
WeatherWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
}
protected void requireWeatherInfo() {
if (!mGoogleApiClient.isConnected())
return;
long timeMs = System.currentTimeMillis();
// The weather info is still up to date.
if ((timeMs - mWeatherInfoReceivedTime) <= mRequireInterval)
return;
// Try once in a min.
if ((timeMs - mWeatherInfoRequiredTime) <= DateUtils.MINUTE_IN_MILLIS)
return;
mWeatherInfoRequiredTime = timeMs;
Wearable.MessageApi.sendMessage(mGoogleApiClient, "", Consts.PATH_WEATHER_REQUIRE, null)
.setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
log("SendRequireMessage:" + sendMessageResult.getStatus());
}
});
}
protected void unregisterTimeZoneService() {
if (!mRegisteredService) {
return;
}
mRegisteredService = false;
//TimeZone
WeatherWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
}
protected void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldUpdateTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
}
}
Any help would be GREATLY appreciated :) (This is also my first real time making a watch face, so please forgive me)
A default constructor accepts zero arguments. You have only a one-argument constructor in WeatherWatchFaceEngine.
Add a zero-argument constructor to WeatherWatchFaceEngine, whose signiture will be public WeatherWatchFaceEngine(){...}
I'm looking at Google's Material Design guidelines and I want add animated action bar. My goal is do something like this:
How can I add transition for action bar's content? I'm using Appcompat to keep backward compatibility.
Update:
I've created an open source library that provides transition/animation support to both View and MenuItem:
MenuItem transition
View transition
Instructions:
On Android Studio, add the code below to Gradle dependencies:
compile 'com.github.kaichunlin.transition:core:0.8.1'
Sample code with explanations:
protected void onCreate(Bundle savedInstanceState) {
//...
//standard onCreate() stuff that creates set configs toolbar, mDrawerLayout & mDrawerToggle
//Use the appropriate adapter that extends MenuBaseAdapter:
DrawerListenerAdapter mDrawerListenerAdapter = new DrawerListenerAdapter(mDrawerToggle, R.id.drawerList);
mDrawerListenerAdapter.setDrawerLayout(mDrawerLayout);
//Add desired transition to the adapter, MenuItemTransitionBuilder is used to build the transition:
//Creates a shared configuration that: applies alpha, the transition effect is applied in a cascading manner (v.s. simultaneously), MenuItems will resets to enabled when transiting, and invalidates menu on transition completion
MenuItemTransitionBuilder builder = MenuItemTransitionBuilder.transit(toolbar).alpha(1f, 0.5f).scale(1f, 0f).cascade(0.3f).visibleOnStartAnimation(true).invalidateOptionOnStopTransition(this, true);
MenuItemTransition mShrinkClose = builder.translationX(0, 30).build();
MenuItemTransition mShrinkOpen = builder.reverse().translationX(0, 30).build();
mDrawerListenerAdapter.setupOptions(this, new MenuOptionConfiguration(mShrinkOpen, R.menu.drawer), new MenuOptionConfiguration(mShrinkClose, R.menu.main));
}
//Let the adapter manage the creation of options menu:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
mDrawerListenerAdapter.onCreateOptionsMenu(this, menu);
return super.onCreateOptionsMenu(menu);
}
Source of the activity implementing the above is here, and a demo app here.
Originally Accepted Answer:
Here's a solution that's more versatile and is exactly how the MenuItem fade-out of Google Drive, Google Docs, Google Sheets, and Google Slides work.
The advantage is that when the user slide in from the left edge of the screen to open the drawer manually, or slide right when the drawer is opened to close it, the animation state is integrated with how the drawer is being opened/closed.
ProgressAnimator.java: This is the meat of the implementation, it translates a float based progression value (0f~1f) into a value that Android Animator understands.
public class ProgressAnimator implements TimeAnimator.TimeListener {
private final List<AnimationControl> animationControls = new ArrayList<>();
private final MenuItem mMenuItem; //TODO shouldn't be here, add animation end listener
private final ImageView mImageView;
private final TimeAnimator mTimeAnim;
private final AnimatorSet mInternalAnimSet;
public ProgressAnimator(Context context, MenuItem mMenuItem) {
if (mMenuItem == null) {
mImageView = null;
} else {
mImageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.menu_animation, null).findViewById(R.id.menu_animation);
mImageView.setImageDrawable(mMenuItem.getIcon());
}
this.mMenuItem = mMenuItem;
this.mInternalAnimSet = new AnimatorSet();
mTimeAnim = new TimeAnimator();
mTimeAnim.setTimeListener(this);
}
public void addAnimatorSet(AnimatorSet mAnimSet, float start, float end) {
animationControls.add(new AnimationControl(mImageView, mAnimSet, start, end));
}
public void addAnimatorSet(Object target, AnimatorSet mAnimSet, float start, float end) {
animationControls.add(new AnimationControl(target, mAnimSet, start, end));
}
public void start() {
ValueAnimator colorAnim = ObjectAnimator.ofInt(new Object() {
private int dummy;
public int getDummy() {
return dummy;
}
public void setDummy(int dummy) {
this.dummy = dummy;
}
}, "dummy", 0, 1);
colorAnim.setDuration(Integer.MAX_VALUE);
mInternalAnimSet.play(colorAnim).with(mTimeAnim);
mInternalAnimSet.start();
if (mMenuItem != null) {
mMenuItem.setActionView(mImageView);
}
for (AnimationControl ctrl : animationControls) {
ctrl.start();
}
}
public void end() {
mTimeAnim.end();
if (mMenuItem != null) {
mMenuItem.setActionView(null);
}
}
public void updateProgress(float progress) {
for (AnimationControl ctrl : animationControls) {
ctrl.updateProgress(progress);
}
}
#Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
for (AnimationControl ctrl : animationControls) {
ctrl.updateState();
}
}
}
AnimationControl.java: Controls the progression of transition.
public class AnimationControl {
private AnimatorSet mAnimSet;
private Object target;
private float start;
private float end = 1.0f;
private float progressWidth;
private long time;
private boolean started;
private long mStartDelay;
private long mDuration;
private long mTotalDuration;
public AnimationControl(AnimatorSet mAnimSet, float start, float end) {
this(null, mAnimSet, start, end);
}
public AnimationControl(Object target, AnimatorSet mAnimSet, float start, float end) {
for (Animator animator : mAnimSet.getChildAnimations()) {
if (!(animator instanceof ValueAnimator)) {
throw new UnsupportedOperationException("Only ValueAnimator and its subclasses are supported");
}
}
this.target = target;
this.mAnimSet = mAnimSet;
mStartDelay = mAnimSet.getStartDelay();
mDuration = mAnimSet.getDuration();
if (mAnimSet.getDuration() >= 0) {
long duration = mAnimSet.getDuration();
for (Animator animator : mAnimSet.getChildAnimations()) {
animator.setDuration(duration);
}
} else {
for (Animator animator : mAnimSet.getChildAnimations()) {
long endTime = animator.getStartDelay() + animator.getDuration();
if (mDuration < endTime) {
mDuration = endTime;
}
}
}
mTotalDuration = mStartDelay + mDuration;
this.start = start;
this.end = end;
progressWidth = Math.abs(end - start);
}
public void start() {
if (target != null) {
for (Animator animator : mAnimSet.getChildAnimations()) {
animator.setTarget(target);
}
}
}
public void updateProgress(float progress) {
if (start < end && progress >= start && progress <= end || start > end && progress >= end && progress <= start) {
if (start < end) {
time = (long) (mTotalDuration * (progress - start) / progressWidth);
} else {
time = (long) (mTotalDuration - mTotalDuration * (progress - end) / progressWidth);
}
time -= mStartDelay;
if (time > 0) {
started = true;
}
Log.e(getClass().getSimpleName(), "updateState: " + mTotalDuration + ";" + time+"/"+start+"/"+end);
} else {
//forward
if (start < end) {
if (progress < start) {
time = 0;
} else if (progress > end) {
time = mTotalDuration;
}
//backward
} else if (start > end) {
if (progress > start) {
time = 0;
} else if (progress > end) {
time = mTotalDuration;
}
}
started = false;
}
}
public void updateState() {
if (started) {
for (Animator animator : mAnimSet.getChildAnimations()) {
ValueAnimator va = (ValueAnimator) animator;
long absTime = time - va.getStartDelay();
if (absTime > 0) {
va.setCurrentPlayTime(absTime);
}
}
}
}
}
ProgressDrawerListener.java: This listens for DrawerLayout state update and setup the required animation.
public class ProgressDrawerListener implements DrawerLayout.DrawerListener {
private final List<ProgressAnimator> mAnimatingMenuItems = new ArrayList<>();
private final Toolbar mToolbar;
private final ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout.DrawerListener mDrawerListener;
private MenuItemAnimation mMenuItemAnimation;
private Animation mAnimation;
private boolean started;
private boolean mOpened;
private Activity mActivity;
private boolean mInvalidateOptionOnOpenClose;
public ProgressDrawerListener(Toolbar mToolbar, ActionBarDrawerToggle mDrawerToggle) {
this.mToolbar = mToolbar;
this.mDrawerToggle = mDrawerToggle;
}
#Override
public void onDrawerOpened(View view) {
mDrawerToggle.onDrawerOpened(view);
clearAnimation();
started = false;
if (mDrawerListener != null) {
mDrawerListener.onDrawerOpened(view);
}
mToolbar.getMenu().setGroupVisible(0, false); //TODO not always needed
mOpened=true;
mActivity.invalidateOptionsMenu();
}
#Override
public void onDrawerClosed(View view) {
mDrawerToggle.onDrawerClosed(view);
clearAnimation();
started = false;
if (mDrawerListener != null) {
mDrawerListener.onDrawerClosed(view);
}
mOpened=false;
mActivity.invalidateOptionsMenu();
}
#Override
public void onDrawerStateChanged(int state) {
mDrawerToggle.onDrawerStateChanged(state);
switch (state) {
case DrawerLayout.STATE_DRAGGING:
case DrawerLayout.STATE_SETTLING:
if (mAnimatingMenuItems.size() > 0 || started) {
break;
}
started = true;
setupAnimation();
break;
case DrawerLayout.STATE_IDLE:
clearAnimation();
started = false;
break;
}
if (mDrawerListener != null) {
mDrawerListener.onDrawerStateChanged(state);
}
}
private void setupAnimation() {
mToolbar.getMenu().setGroupVisible(0, true); //TODO not always needed
mAnimatingMenuItems.clear();
for (int i = 0; i < mToolbar.getChildCount(); i++) {
final View v = mToolbar.getChildAt(i);
if (v instanceof ActionMenuView) {
int menuItemCount = 0;
int childCount = ((ActionMenuView) v).getChildCount();
for (int j = 0; j < childCount; j++) {
if (((ActionMenuView) v).getChildAt(j) instanceof ActionMenuItemView) {
menuItemCount++;
}
}
for (int j = 0; j < childCount; j++) {
final View innerView = ((ActionMenuView) v).getChildAt(j);
if (innerView instanceof ActionMenuItemView) {
MenuItem mMenuItem = ((ActionMenuItemView) innerView).getItemData();
ProgressAnimator offsetAnimator = new ProgressAnimator(mToolbar.getContext(), mMenuItem);
if(mMenuItemAnimation!=null) {
mMenuItemAnimation.setupAnimation(mMenuItem, offsetAnimator, j, menuItemCount);
}
if(mAnimation!=null) {
mAnimation.setupAnimation(offsetAnimator);
}
offsetAnimator.start();
mAnimatingMenuItems.add(offsetAnimator);
}
}
}
}
onDrawerSlide(null, mOpened ? 1f : 0f);
Log.e(getClass().getSimpleName(), "setupAnimation: "+mAnimatingMenuItems.size()); //TODO
}
#Override
public void onDrawerSlide(View view, float slideOffset) {
for (ProgressAnimator ani : mAnimatingMenuItems) {
ani.updateProgress(slideOffset);
}
if(view==null) {
return;
}
mDrawerToggle.onDrawerSlide(view, slideOffset);
if (mDrawerListener != null) {
mDrawerListener.onDrawerSlide(view, slideOffset);
}
}
private void clearAnimation() {
for (ProgressAnimator ani : mAnimatingMenuItems) {
ani.end();
}
mAnimatingMenuItems.clear();
}
public void setDrawerListener(DrawerLayout.DrawerListener mDrawerListener) {
this.mDrawerListener = mDrawerListener;
}
public MenuItemAnimation getMenuItemAnimation() {
return mMenuItemAnimation;
}
public void setMenuItemAnimation(MenuItemAnimation mMenuItemAnimation) {
this.mMenuItemAnimation = mMenuItemAnimation;
}
public Animation getAnimation() {
return mAnimation;
}
public void setAnimation(Animation mAnimation) {
this.mAnimation = mAnimation;
}
public void setmInvalidateOptionOnOpenClose(Activity activity, boolean invalidateOptionOnOpenClose) {
mActivity=activity;
mInvalidateOptionOnOpenClose = invalidateOptionOnOpenClose;
}
public interface MenuItemAnimation {
public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount);
}
public interface Animation {
public void setupAnimation(ProgressAnimator offsetAnimator);
}
}
Set up in Activity: The example code below switches between two different menu options between opened and closed state. Optionally add offsetDrawerListener.setDrawerListener(DrawerListener) if you need to have your own DrawerListener.:
#Override
protected void onCreate(Bundle savedInstanceState) {
//other init
mProgressDrawerListener =new ProgressDrawerListener(toolbar, mDrawerToggle);
mProgressDrawerListener.setmInvalidateOptionOnOpenClose(this, true);
mOpenAnimation = new ProgressDrawerListener.MenuItemAnimation() {
#Override
public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount) {
MainActivity.this.setupAnimation(true, offsetAnimator, itemIndex);
}
};
mCloseAnimation = new ProgressDrawerListener.MenuItemAnimation() {
#Override
public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount) {
MainActivity.this.setupAnimation(false, offsetAnimator, itemIndex);
}
};
mDrawerLayout.setDrawerListener(mProgressDrawerListener);
}
//customize your animation here
private void setupAnimation(boolean open, ProgressAnimator offsetAnimator, int itemIndex) {
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0f),
ObjectAnimator.ofFloat(null, "scaleX", 1.0f, 0f)
);
set.setStartDelay(itemIndex * 200);
set.setDuration(1000 - itemIndex * 200); //not the actual time the animation will be played
if(open) {
offsetAnimator.addAnimatorSet(set, 0, 1);
} else {
offsetAnimator.addAnimatorSet(set, 1, 0);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
if(mDrawerLayout.isDrawerOpen(findViewById(R.id.drawerList))) {
getMenuInflater().inflate(R.menu.drawer, menu);
mProgressDrawerListener.setMenuItemAnimation(
mCloseAnimation);
} else {
getMenuInflater().inflate(R.menu.main, menu);
mProgressDrawerListener.setMenuItemAnimation(
mOpenAnimation);
mDrawerLayout.setDrawerListener(mProgressDrawerListener);
}
return super.onCreateOptionsMenu(menu);
}
menu_animation.xml: This is to get the custom ActionView to have the same layout as the view used by MenuIem
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/menu_animation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="#*android:dimen/action_button_min_width"
android:padding="8dp"
style="#style/Widget.AppCompat.ActionBar" />
I think I finally found your answer. This was way harder to find than I thought. If you take a look at this link: http://suhan.in/material-design-toolbar-animation/
The first one explains how this is done.
Below you find my own code snippet of how it could be done with only the menu items:
for(int i = 0; i < toolbarView.getChildCount(); i++)
{
final View v = toolbarView.getChildAt(i);
if(v instanceof ActionMenuView)
{
for(int j = 0; j < ((ActionMenuView)v).getChildCount(); j++)
{
final View innerView = ((ActionMenuView)v).getChildAt(j);
if(innerView instanceof ActionMenuItemView)
{
innerView.setTranslationY(-30);
innerView.animate().setStartDelay(100 + (j * 10)).setDuration(200).translationY(0);
}
}
}
}
This is the animation for the Y-axis. You can also add the animation for size, which I think they do in the design guidelines. Also if you don't want them to start simultaniously, you can set startDelay and add extra like this: setStartDelay(i * 10). This way each item starts the animation a little later. I already put this in the code snippet, but tweak it as how you would like it.
I am trying to create an image viewer which can load images from given URLs.The code below implement the User Interface.
My intention is to let user Zoom the image and move to next image with a Swipe event.But the problem is that when i zoom and then swipe , instead of showing the remaining portion , it moves to the next image.
I tried using requestDisallowInterceptTouchEvent in TouchImageView's(https://github.com/MikeOrtiz/TouchImageView) onTouchListener .After this the remaining portion could show but now i cannot go to the next page. I was wondering how this can be achieved as the event can only go to either TouchView or PageAdapter
public class PageActivity extends Activity {
private int numPages = 33;
private TouchImageView[] imageViews = new TouchImageView[numPages];
private String URL = "http://www.smbc-comics.com/comics/200905";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewPager viewPager = new ViewPager(this);
for (int i = 0; i < numPages; i++) {
imageViews[i] = new TouchImageView(this);
imageViews[i].setBackgroundResource(R.drawable.banke);
imageViews[i].setMaxZoom(4f);
}
setContentView(viewPager);
ImagePagerAdapter adapter = new ImagePagerAdapter();
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(2);
}
#SuppressWarnings("unused")
private class ImagePagerAdapter extends PagerAdapter {
#Override
public int getCount() {
return numPages;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((TouchImageView) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Context context = PageActivity.this;
String pageURL = URL;
if (imageViews[position].getDrawable() == null) {
ImageFetcher imagefetcher = new ImageFetcher();
imagefetcher.execute(
pageURL + String.format("%02d", position+1) + ".gif",
String.valueOf(position));
}
((ViewPager) container).addView(imageViews[position], 0);
return imageViews[position];
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((TouchImageView) object);
imageViews[position].setImageDrawable(null);
}
}
public class ImageFetcher extends AsyncTask<String, Integer, Drawable> {
int fillthisPos;
public Drawable doInBackground(String... urls) {
try {
InputStream is = (InputStream) new URL(urls[0]).getContent();
fillthisPos = Integer.parseInt(urls[1]);
Drawable d = Drawable.createFromStream(is, "src name");
return d;
} catch (Exception e) {
return null;
}
}
#Override
protected void onPostExecute(Drawable result) {
super.onPostExecute(result);
imageViews[fillthisPos].setImageDrawable(result);
result = null;
}
}
}
You can add following code in TouchImageView class:
public boolean isZoomed () {
return (normalizedScale > minScale);
}
private void onSwipeEvent(MotionEvent event) {
boolean zoomed = this.isZoomed();
if (!zoomed && (swipeLen > 0)) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
swipeStartPos = (int) event.getRawX();
}
else if (event.getAction() == MotionEvent.ACTION_MOVE) {
int distance = ((int) event.getRawX()) - swipeStartPos;
int swipeVector = SWIPE_RIGHT;
if (distance < 0) swipeVector = SWIPE_LEFT;
if (Math.abs(distance) > swipeLen) {
onSwipeHandler.onSwipe(swipeVector);
swipeStartPos = (int) event.getRawX();
this.setScaleX(1f);
}
else {
int swipeStDist = swipeLen - Math.round(((swipeLen / 100) * 50));
if (Math.abs(distance) > swipeStDist) {
this.setScaleX(0.98f);
}
}
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
swipeStartPos = (int) event.getRawX();
this.setScaleX(1f);
}
}
}
public static int SWIPE_LEFT = 0;
public static int SWIPE_RIGHT = 1;
public int swipeStartPos = 0;
public onSwipeListener onSwipeHandler = new onSwipeListener() {
public void onSwipe(int vector) {}
};
public static int swipeLen = 0;
public void setOnSwipeListener(onSwipeListener c, int swipeLength) {
onSwipeHandler = c;
swipeLen = swipeLength;
}
public interface onSwipeListener {
public void onSwipe(int vector);
}
And also in TouchImageView below line 636 add onSwipeEvent(event) like this:
......
setImageMatrix(matrix);
onSwipeEvent(event);
//
// indicate event was handled
//
return true;
...........
After this from you code you can add swipe event listener, like this:
imageView.setOnSwipeListener(new TouchImageView.onSwipeListener() {
#Override
public void onSwipe(int vector) {
if (vector == TouchImageView.SWIPE_LEFT) {
Log.d("swipe", "swipe left!");
}
else if (vector == TouchImageView.SWIPE_RIGHT) {
Log.d("swipe", "swipe right!");
}
}
}, 200); //length of swiping - 200 dip
This onSwipeListener ignore onswipe where image is zoomed+. It work for me.