Drag and drop + custom drawing in Android - java

I am working on something that needed custom drag-and-drop functionality, so I have been subclassing View, doing a bunch of math in response to touch events, and then rendering everything manually through code on the canvas in onDraw. Now, the more functionality I add, the more the code is growing out of control and I find myself writing a ton more code than I would expect to write in a high level environment like Android.
Is this how it's done, or am I missing something? If I'm not doing anything fancy in the UI, the framework handles the majority of my interactions. Built-in controls handle the touches and drags, and my code is pretty much limited to business logic and data. Is there a way to leverage the power of some of the UI controls and things like animations while also doing some of it manually in the onDraw canvas? Is there an accepted standard of when to use one or the other (if indeed the two approaches can be mixed)?

I use drag and drop in my music player application! I give to user the ability to move an song from an playlist to an other playlist. It is really nice and simple for the user. I start the drag event for my view when user make an long tap on an song or when an option from an menu is selected!
This is my class:
package com.liviu.app.smpp.gui;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.liviu.app.smpp.R;
import com.liviu.app.smpp.listeners.CollisionListener;
public class SongItemView extends RelativeLayout implements OnClickListener {
// data
private String TAG = "SongItemView";
private Context context;
private LayoutInflater lInflater;
private String title;
private int id;
private int maxHeight = 410;
private int mCurX;
private int mCurY;
//listeners
private CollisionListener onCollisionListener = null;
// views
private View v;
public SongItemView(Context ctx, String title_, int id_) {
super(ctx);
context = ctx;
lInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = lInflater.inflate(R.layout.song_item_view, null);
title = title_;
id = id_;
((TextView)v.findViewById(R.id.siv_title)).setText(title);
addView(v, new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
#Override
public void onClick(View v) {
Log.e(TAG, "clicked! " + ((TextView)v.findViewById(R.id.piv_title)).getText().toString());
}
public View getView(){
return v;
}
public String getPlsName() {
return title;
}
public int getID() {
return id;
}
public void setTitle(String title_){
((TextView)v.findViewById(R.id.siv_title)).setText(title_);
title = title_;
}
public void setID(int id_) {
id = id_;
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
mCurX = (int) event.getRawX();
mCurY = (int) event.getRawY();
int action = event.getAction();
if (action == MotionEvent.ACTION_MOVE)
{
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.leftMargin = mCurX;
params.topMargin = mCurY;
this.setLayoutParams(params);
if(this.getTop() >= maxHeight)
{
Log.e(TAG, "Collision!!!!");
if(onCollisionListener != null){
onCollisionListener.onCollision(this);
}
}
}
return true;
}
public void setOnCollisionListener(CollisionListener listener){
onCollisionListener = listener;
}
public void setMaxHeight(int height){
maxHeight = height;
}
public int getmCurX() {
return mCurX;
}
public int getmCurY() {
return mCurY;
}
public int getMaxHeight() {
return maxHeight;
}
}
I hope this will help a bit.
Thanks!

Related

Android MediaBrowserService customize audio src and meta data

I am building a audio player i have completed all the Songs browsing (UI) activities and to handle audio playback i am using google sample audio player this and i read all the src from this sample and there is class MusicLibrary in sample code and it contains a sample list of songs like below
public class MusicLibrary {
private static final TreeMap<String, MediaMetadataCompat> music = new TreeMap<>();
private static final HashMap<String, Integer> albumRes = new HashMap<>();
private static final HashMap<String, String> musicFileName = new HashMap<>();
static {
createMediaMetadataCompat(
"Jazz_In_Paris",
"Jazz in Paris",
"Media Right Productions",
"Jazz & Blues",
"Jazz",
103,
TimeUnit.SECONDS,
"http://www.noiseaddicts.com/samples_1w72b820/2537.mp3",
R.drawable.album_jazz_blues,
"https://upload.wikimedia.org/wikipedia/en/a/ac/Audioslave_-_Audioslave.jpg");
createMediaMetadataCompat(
"The_Coldest_Shoulder",
"The Coldest Shoulder",
"The 126ers",
"Youtube Audio Library Rock 2",
"Rock",
160,
TimeUnit.SECONDS,
"http://www.noiseaddicts.com/samples_1w72b820/4201.mp3",
R.drawable.album_youtube_audio_library_rock_2,
"https://theflagcompany.in/wp-content/uploads/2018/10/Indian-National-Flag-300x300.jpeg");
}
but i am unable to find a method which shows how to add a song in current playlist from other activities for example in my current app there is search activity where user can search any song from internet and i want to add track from this search activity by two different ways 1. add and play song 2. only add song in the end of playlist.
Sample code track object use album art image from drawable folder but in my app i need to show album art from internet (Last FM API) not from local folder i tried to edit MusicLibrary class and created a bitmap from URI unable to make it work so this is alos i need to modify in sample code.
Sample code contains only on simple activity where when you open it opens a audio player interface but in my app in HomeActivity there are some fragments and a mini player at bottom now like below
Please help me with these two things
add songs to player from my code
use internet image for METADATA_KEY_ALBUM_ART
i just want to merge these two things and i am new to android so no luck please guide me so that i can complete my project. Thanks in advance :)
My Main Activity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//add song in sample player service from this method
public void addSongQue(Song song) {
title = song.getTile;
artist = song.getArtist;
mp3Url = song.getSrc;
albumArtImage = song.getAlbumArt;
.......
...
}
}
Sample Code Player Activity full sample src is here
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.android.mediasession.R;
import com.example.android.mediasession.client.MediaBrowserHelper;
import com.example.android.mediasession.service.MusicService;
import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ImageView mAlbumArt;
private TextView mTitleTextView;
private TextView mArtistTextView;
private ImageView mMediaControlsImage;
private MediaSeekBar mSeekBarAudio;
private MediaBrowserHelper mMediaBrowserHelper;
private boolean mIsPlaying;
//https://github.com/googlesamples/android-MediaBrowserService
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTitleTextView = findViewById(R.id.song_title);
mArtistTextView = findViewById(R.id.song_artist);
mAlbumArt = findViewById(R.id.album_art);
mMediaControlsImage = findViewById(R.id.media_controls);
mSeekBarAudio = findViewById(R.id.seekbar_audio);
final ClickListener clickListener = new ClickListener();
findViewById(R.id.button_previous).setOnClickListener(clickListener);
findViewById(R.id.button_play).setOnClickListener(clickListener);
findViewById(R.id.button_next).setOnClickListener(clickListener);
mMediaBrowserHelper = new MediaBrowserConnection(this);
mMediaBrowserHelper.registerCallback(new MediaBrowserListener());
}
#Override
public void onStart() {
super.onStart();
mMediaBrowserHelper.onStart();
}
#Override
public void onStop() {
super.onStop();
mSeekBarAudio.disconnectController();
mMediaBrowserHelper.onStop();
}
/**
* Convenience class to collect the click listeners together.
* <p>
* In a larger app it's better to split the listeners out or to use your favorite
* library.
*/
private class ClickListener implements View.OnClickListener {
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_previous:
mMediaBrowserHelper.getTransportControls().skipToPrevious();
break;
case R.id.button_play:
if (mIsPlaying) {
mMediaBrowserHelper.getTransportControls().pause();
} else {
mMediaBrowserHelper.getTransportControls().play();
}
break;
case R.id.button_next:
mMediaBrowserHelper.getTransportControls().skipToNext();
break;
}
}
}
/**
* Customize the connection to our {#link android.support.v4.media.MediaBrowserServiceCompat}
* and implement our app specific desires.
*/
private class MediaBrowserConnection extends MediaBrowserHelper {
private MediaBrowserConnection(Context context) {
super(context, MusicService.class);
}
#Override
protected void onConnected(#NonNull MediaControllerCompat mediaController) {
mSeekBarAudio.setMediaController(mediaController);
}
#Override
protected void onChildrenLoaded(#NonNull String parentId,
#NonNull List<MediaBrowserCompat.MediaItem> children) {
super.onChildrenLoaded(parentId, children);
final MediaControllerCompat mediaController = getMediaController();
// Queue up all media items for this simple sample.
for (final MediaBrowserCompat.MediaItem mediaItem : children) {
mediaController.addQueueItem(mediaItem.getDescription());
}
// Call prepare now so pressing play just works.
mediaController.getTransportControls().prepare();
}
}
/**
* Implementation of the {#link MediaControllerCompat.Callback} methods we're interested in.
* <p>
* Here would also be where one could override
* {#code onQueueChanged(List<MediaSessionCompat.QueueItem> queue)} to get informed when items
* are added or removed from the queue. We don't do this here in order to keep the UI
* simple.
*/
private class MediaBrowserListener extends MediaControllerCompat.Callback {
#Override
public void onPlaybackStateChanged(PlaybackStateCompat playbackState) {
mIsPlaying = playbackState != null &&
playbackState.getState() == PlaybackStateCompat.STATE_PLAYING;
mMediaControlsImage.setPressed(mIsPlaying);
}
#Override
public void onMetadataChanged(MediaMetadataCompat mediaMetadata) {
if (mediaMetadata == null) {
return;
}
mTitleTextView.setText(
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
mArtistTextView.setText(
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
mAlbumArt.setImageBitmap(MusicLibrary.getAlbumBitmap(
MainActivity.this,
mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)));
}
#Override
public void onSessionDestroyed() {
super.onSessionDestroyed();
}
#Override
public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
super.onQueueChanged(queue);
}
}
}
MusicLibrary Class where two sample songs
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaMetadataCompat;
import com.example.android.mediasession.BuildConfig;
import com.example.android.mediasession.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
public class MusicLibrary {
private static final TreeMap<String, MediaMetadataCompat> music = new TreeMap<>();
private static final HashMap<String, Integer> albumRes = new HashMap<>();
private static final HashMap<String, String> musicFileName = new HashMap<>();
static {
createMediaMetadataCompat(
"Jazz_In_Paris",
"Jazz in Paris",
"Media Right Productions",
"Jazz & Blues",
"Jazz",
103,
TimeUnit.SECONDS,
"http://www.noiseaddicts.com/samples_1w72b820/2537.mp3",
R.drawable.album_jazz_blues,
"https://upload.wikimedia.org/wikipedia/en/a/ac/Audioslave_-_Audioslave.jpg");
createMediaMetadataCompat(
"The_Coldest_Shoulder",
"The Coldest Shoulder",
"The 126ers",
"Youtube Audio Library Rock 2",
"Rock",
160,
TimeUnit.SECONDS,
"http://www.noiseaddicts.com/samples_1w72b820/4201.mp3",
R.drawable.album_youtube_audio_library_rock_2,
"https://theflagcompany.in/wp-content/uploads/2018/10/Indian-National-Flag-300x300.jpeg");
}
public static String getRoot() {
return "root";
}
private static String getAlbumArtUri(String albumArtResName) {
return ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
BuildConfig.APPLICATION_ID + "/drawable/" + albumArtResName;
}
public static String getMusicFilename(String mediaId) {
return musicFileName.containsKey(mediaId) ? musicFileName.get(mediaId) : null;
}
private static int getAlbumRes(String mediaId) {
return albumRes.containsKey(mediaId) ? albumRes.get(mediaId) : 0;
}
public static Bitmap getAlbumBitmap(Context context, String mediaId) {
return BitmapFactory.decodeResource(context.getResources(),
MusicLibrary.getAlbumRes(mediaId));
}
public static List<MediaBrowserCompat.MediaItem> getMediaItems() {
List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
for (MediaMetadataCompat metadata : music.values()) {
result.add(
new MediaBrowserCompat.MediaItem(
metadata.getDescription(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
}
return result;
}
public static MediaMetadataCompat getMetadata(Context context, String mediaId) {
MediaMetadataCompat metadataWithoutBitmap = music.get(mediaId);
Bitmap albumArt = getAlbumBitmap(context, mediaId);
// Since MediaMetadataCompat is immutable, we need to create a copy to set the album art.
// We don't set it initially on all items so that they don't take unnecessary memory.
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
for (String key :
new String[]{
MediaMetadataCompat.METADATA_KEY_MEDIA_ID,
MediaMetadataCompat.METADATA_KEY_ALBUM,
MediaMetadataCompat.METADATA_KEY_ARTIST,
MediaMetadataCompat.METADATA_KEY_GENRE,
MediaMetadataCompat.METADATA_KEY_TITLE
}) {
builder.putString(key, metadataWithoutBitmap.getString(key));
}
builder.putLong(
MediaMetadataCompat.METADATA_KEY_DURATION,
metadataWithoutBitmap.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
return builder.build();
}
private static void createMediaMetadataCompat(
String mediaId,
String title,
String artist,
String album,
String genre,
long duration,
TimeUnit durationUnit,
String musicFilename,
int albumArtResId,
String albumArtResName) {
music.put(
mediaId,
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION,
TimeUnit.MILLISECONDS.convert(duration, durationUnit))
.putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
.putString(
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
getAlbumArtUri(albumArtResName))
.putString(
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,
getAlbumArtUri(albumArtResName))
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.build());
albumRes.put(mediaId, albumArtResId);
musicFileName.put(mediaId, musicFilename);
}
}

TextWatcher implementation with RadioButtons

Please see attached image and code snippet to aid in explanation.
From the attached image I would like the user to enter a cost, quantity and select either Include Tax or Exclude tax and a new cost is automatically generated where indicated without pressing a Button, but to no avail I am unable to do this. Someone please help. Thanks
See Image Here
After implementing the Changes that were suggested and trying to enter an input in the cost field I was met with the error seen below. Please provide additional feedback. Thanks
Error image
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
public class Calculator extends Fragment {
private static EditText itemText, editCost, editQuantity, calCost, rTax;
private static RadioGroup rGroup;
private static RadioButton rb;
View gView;
private double bTotal = 0, aTotal = 0, trueCost = 0, taxValue = 16.5, cost = 0, newCost = 0;
private int quantity = 1;
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.US);
DecimalFormat decimalFormat = new DecimalFormat("###,###.##", symbols);
CalculatorListener activityCommander;
public interface CalculatorListener {
void addtoCart(String itemName, int qty, double beforeTax, double afterTax, double bTotal, double aTotal);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
activityCommander = (CalculatorListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString());
}
}
public Calculator() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
gView = inflater.inflate(R.layout.fragment_calculator, container, false);
editCost = (EditText) gView.findViewById(R.id.editcost);
itemText = (EditText) gView.findViewById(R.id.itemText);
editQuantity = (EditText) gView.findViewById(R.id.editquantity);
calCost = (EditText) gView.findViewById(R.id.calcost);
rTax = (EditText) gView.findViewById(R.id.rtax);
rGroup = (RadioGroup) gView.findViewById(R.id.rgroup);
final ImageButton FieldButton = (ImageButton) gView.findViewById(R.id.FieldButton);
final ImageButton TaxButton = (ImageButton) gView.findViewById(R.id.TaxButton);
final ImageButton CalButton = (ImageButton) gView.findViewById(R.id.CalButton);
rTax.setEnabled(false);
calCost.setEnabled(false);
rGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
rb = (RadioButton)gView.findViewById(checkedId);
}
});
editCost.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
try{
update();
}catch (NumberFormatException e)
{
e.printStackTrace();
}
}
});
editQuantity.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
try{
update();
}catch (NumberFormatException e)
{
e.printStackTrace();
}
}
});
FieldButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
clearfield();
}
}
);
TaxButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
adjtax();
}
}
);
CalButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
//toCart();
}
}
);
return gView;
}
public void clearfield() {
editCost.setText("");
editCost.setBackgroundResource(R.drawable.edittxt);
editQuantity.setText("");
editQuantity.setBackgroundResource(R.drawable.edittxt);
calCost.setText("");
calCost.setBackgroundResource(R.drawable.edittxt);
itemText.setText("");
itemText.setBackgroundResource(R.drawable.edittxt);
rGroup.clearCheck();
}
public void adjtax() {
editCost.setBackgroundResource(R.drawable.edittxt);
editQuantity.setBackgroundResource(R.drawable.edittxt);
calCost.setBackgroundResource(R.drawable.edittxt);
itemText.setBackgroundResource(R.drawable.edittxt);
rTax.setEnabled(true);
rTax.setText("");
rTax.setBackgroundResource(R.drawable.edittxtfocus);
rTax.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
rTax.setEnabled(true);
} else {
rTax.setEnabled(false);
v.setBackgroundResource(R.drawable.edittxt);
}
}
});
}
public void update(){
if (rTax.getText().toString().isEmpty()) {
taxValue = 16.5;
} else if (!rTax.getText().toString().isEmpty()) {
taxValue = Double.parseDouble(rTax.getText().toString());
}
//CHECKS THE TAX VALUE IF IT NEEDS TO BE CONVERTED
if (taxValue > 1) {
taxValue = taxValue / 100;
} else {
taxValue = taxValue * 1;
}
//CUSTOM VALIDATOR FOR QUANTITY FIELD
if (editQuantity.getText().toString().isEmpty()) {
quantity = 1;
} else if (!editQuantity.getText().toString().isEmpty()) {
quantity = Integer.parseInt(editQuantity.getText().toString());
}
if(rb.getText() == "Include Tax"){
newCost = (((cost = Double.parseDouble(editCost.getText().toString())) * taxValue) + cost) * quantity;
calCost.setText(decimalFormat.format(newCost).toString());
}
else if(rb.getText() == "Exclude Tax"){
newCost = ((cost = Double.parseDouble(editCost.getText().toString())) * quantity);
calCost.setText(decimalFormat.format(newCost).toString());
}
trueCost = cost * quantity;
bTotal = trueCost;
aTotal = newCost;
}
}
Move the rgroup.setOnCheckedChangeListener out of the update() method and into the onCreateView(). You should not have to set the listener every time the text has been updated.
The update method called after text entry can probably just update the tax value if a valid value has been entered and either the check boxes have been selected.
Update with another suggestion
I would lookup the radio button by comparing text as you are doing, some time in the future you may want to change the text in the resource file or apply another locale and this code will stop working.
if(rb.getText() == "Include Tax")
I would suggest comparing against the id itself:
if (checkedId == R.id.radio1 )
Another Suggestion:
Consider changing your variable names to lead with a lower case character. Leading with an upper case letter makes it look like either class name or a constant and make the code a bit more difficult to read.
private EditText itemText;
private EditText editCost;
private EditText editQuantity;
private EditText calcost;
private EdtiText rTax;
You can also remove all(some) the focus change listeners you have and set those attributes in the android drawable resources. Take a look here.
Update 9/9/2016 22:22
A bit better, but as you've found out calls to update can throw a null pointer if rb had never been initialized. You should check for a null rb in the update method and give the user a notice to select an option. You could also assign rb to either of the two values from the start in the onCreateView, so it is never null.
You should probably also add a call to update() after setting rb in the radio group callback. This will allow the screen to update as soon as they choose an option.

WindowManager updating content causes view stacking

EDIT: Even stranger. Setting the pixel format to translucent rather than opaque seems to have fixed it, i at least am unable to see the "stacked" numbers.
Very strange behavior.
I am using a service to draw a system_overlay view. The view adds and displays just fine.
This view is meant to be a countdown timer, so I need to update the text every second. I use a handler calling postDelayed to handle that, and call textView.setText("CONTENT") from the runnable being executed by the handler.
This is where it gets weird.
The text updates, but seems to be stacking. I see 00:00 under the 00:01, etc etc. Each tick causes another layer.
I have tested this code and view in a standard activity and the text renders perfect, no "stacks". Its not until its being added via the WindowManager that the behavior is problematic.
TimerView.java
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.t3hh4xx0r.lifelock.widgets;
import java.util.concurrent.TimeUnit;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.os.Handler;
import android.os.IBinder;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.t3hh4xx0r.lifelock.R;
import com.t3hh4xx0r.lifelock.services.TimerDrawerService;
/**
* View used to draw a running timer.
*/
public class TimerView extends FrameLayout {
int alpha = 100;
TimerDrawerService.ServiceBinder drawerBinder;
PointF firstFinger;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
firstFinger = new PointF(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
PointF newFinger = new PointF(event.getX(), event.getY());
float distance = newFinger.x - firstFinger.x;
float part = Math.abs(distance);
float percentOfMaxTraveled = (part * 100) / getWidth();
int nextAlpha = 100 - Float.valueOf(percentOfMaxTraveled).intValue();
if (nextAlpha < 20) {
Toast.makeText(getContext(), "Dismissed", Toast.LENGTH_LONG).show();
if (drawerBinder != null) {
drawerBinder.remove();
}
return true;
}
if (nextAlpha < alpha) {
alpha = nextAlpha;
}
Log.d("THE PERCENT TRAVELED", String.valueOf(percentOfMaxTraveled) + " : " + String.valueOf(alpha));
this.invalidate();
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(),
alpha, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
super.onDraw(canvas);
}
/**
* Interface to listen for changes on the view layout.
*/
public interface ChangeListener {
/** Notified of a change in the view. */
public void onChange();
}
private static final long DELAY_MILLIS = 1000;
private final TextView mMinutesView;
private final TextView mSecondsView;
private final int mWhiteColor;
private final int mRedColor;
private final Handler mHandler = new Handler();
private final Runnable mUpdateTextRunnable = new Runnable() {
#Override
public void run() {
if (mRunning) {
mHandler.postDelayed(mUpdateTextRunnable, DELAY_MILLIS);
updateText();
}
}
};
private final Timer mTimer;
private final Timer.TimerListener mTimerListener = new Timer.TimerListener() {
#Override
public void onStart() {
mRunning = true;
long delayMillis = Math.abs(mTimer.getRemainingTimeMillis())
% DELAY_MILLIS;
if (delayMillis == 0) {
delayMillis = DELAY_MILLIS;
}
mHandler.postDelayed(mUpdateTextRunnable, delayMillis);
}
};
private boolean mRunning;
private boolean mRedText;
private ChangeListener mChangeListener;
public TimerView(Context context) {
this(context, null, 0);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof TimerDrawerService.ServiceBinder) {
drawerBinder = (com.t3hh4xx0r.lifelock.services.TimerDrawerService.ServiceBinder) service;
}
// No need to keep the service bound.
getContext().unbindService(this);
}
#Override
public void onServiceDisconnected(ComponentName name) {
// Nothing to do here.
}
};
public TimerView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
context.bindService(new Intent(context, TimerDrawerService.class), mConnection, 0);
LayoutInflater.from(context).inflate(R.layout.timer, this);
mMinutesView = (TextView) findViewById(R.id.minutes);
mSecondsView = (TextView) findViewById(R.id.seconds);
mWhiteColor = context.getResources().getColor(android.R.color.white);
mRedColor = Color.RED;
mTimer = new Timer();
mTimer.setListener(mTimerListener);
mTimer.setDurationMillis(0);
}
public Timer getTimer() {
return mTimer;
}
/**
* Set a {#link ChangeListener}.
*/
public void setListener(ChangeListener listener) {
mChangeListener = listener;
}
/**
* Updates the text from the Timer's value.
*/
private void updateText() {
long remainingTimeMillis = mTimer.getRemainingTimeMillis();
if (remainingTimeMillis > 0) {
mRedText = false;
// Round up: x001 to (x + 1)000 milliseconds should resolve to x
// seconds.
remainingTimeMillis -= 1;
remainingTimeMillis += TimeUnit.SECONDS.toMillis(1);
} else {
mRedText = !mRedText;
remainingTimeMillis = Math.abs(remainingTimeMillis);
}
if (mRedText) {
// Sync the sound with the red text.
}
updateText(remainingTimeMillis, mRedText ? mRedColor : mWhiteColor);
}
/**
* Updates the displayed text with the provided values.
*/
private void updateText(long timeMillis, int textColor) {
timeMillis %= TimeUnit.HOURS.toMillis(1);
mMinutesView.setText(String.format("%02d",
TimeUnit.MILLISECONDS.toMinutes(timeMillis)));
mMinutesView.setTextColor(textColor);
timeMillis %= TimeUnit.MINUTES.toMillis(1);
mSecondsView.setText(String.format("%02d",
TimeUnit.MILLISECONDS.toSeconds(timeMillis)));
mSecondsView.setTextColor(textColor);
if (mChangeListener != null) {
mChangeListener.onChange();
}
}
public void showMessage(boolean didGood) {
// mTipView.setText((didGood ? "Good" : "Bad") + " job!");
}
public void setLocked(boolean b) {
if (b) {
((WindowManager.LayoutParams) getLayoutParams()).type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
} else {
((WindowManager.LayoutParams) getLayoutParams()).type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
}
}
}
TimerDrawerService
package com.t3hh4xx0r.lifelock.services;
/*
Copyright 2011 jawsware international
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.t3hh4xx0r.lifelock.objects.Peek;
import com.t3hh4xx0r.lifelock.widgets.TimerView;
public class TimerDrawerService extends Service {
TimerView root;
Peek currentInstance;
private ServiceBinder mBinder = new ServiceBinder();
public class ServiceBinder extends Binder {
public TimerView getRoot() {
return root;
}
public void remove() {
removeViews();
}
public void add() {
addViews();
}
}
static public void start(Context c, Peek currentInstance) {
Intent i = new Intent(c, TimerDrawerService.class);
i.putExtra("peek", currentInstance);
c.startService(i);
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public void onCreate() {
super.onCreate();
Log.d("CRATING VIEW HERE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
"NOW MAN");
root = new TimerView(this);
root.getTimer().setDurationMillis(90 * 1000);
root.getTimer().start();
addViews();
}
public void addViews() {
((WindowManager) getSystemService(Context.WINDOW_SERVICE)).addView(
root, getLayoutParams());
}
public static boolean isRunning(Context c) {
ActivityManager manager = (ActivityManager) c
.getSystemService(ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager
.getRunningServices(Integer.MAX_VALUE)) {
if ("com.t3hh4xx0r.lifelock.service.TimerDrawerService"
.equals(service.service.getClassName())) {
return true;
}
}
return false;
}
private WindowManager.LayoutParams getLayoutParams() {
LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, 0,
PixelFormat.OPAQUE);
layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
layoutParams.gravity = Gravity.CENTER;
return layoutParams;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
currentInstance = (Peek) intent.getSerializableExtra("peek");
return START_STICKY;
}
public void removeViews() {
((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.removeView(root);
}
}

Android ItemizedOverlay scaling canvas

i am trying to scale pictures in a Android map itemizedOverlay,
i got it working to the point where i can see 10 pictures, i got zoomControle but nothing else really,
this is the MapItems class that extends ItemizedOverlay, optimizations is welcome
import java.util.ArrayList;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
public class MapItems extends ItemizedOverlay
{
private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();
Context mContext;
public MapItems(Drawable defaultMarker)
{
super(boundCenterBottom(defaultMarker));
}
#Override
public void draw(android.graphics.Canvas canvas,MapView mapView,boolean shadow)
{
/*
Log.d("MapAc", String.valueOf(mapView.getZoomLevel()));
if(mapView.getZoomLevel() > 20)
{
Log.d("MapAc", "scaling up");
canvas.scale(1.2f, 1.2f);
}
*/
super.draw(canvas,mapView,false);
}
public MapItems(Context context)
{
super(boundCenterBottom(context.getResources().getDrawable(R.drawable.app_icon_clean)));
mContext = context;
}
public void addOverlay(OverlayItem overlay)
{
mOverlays.add(overlay);
populate();
}
public void clearOverlay()
{
mOverlays.clear();
}
#Override
protected OverlayItem createItem(int i)
{
return mOverlays.get(i);
}
#Override
public int size()
{
return mOverlays.size();
}
#Override
protected boolean onTap(int index)
{
/* ToDo
OverlayItem item = mOverlays.get(index);
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle(item.getTitle());
dialog.setMessage(item.getSnippet());
dialog.show();
*/
return true;
}
}
i have been trying to scale in the draw method, using canvas.Scale, however this seems to redraw the canvas in another location, together with the old canvas"in its original size",
i am not sure if i am approaching this problem from the right angle, or if it is simply a matter of clearing the screen, i have been using a few days to figure this out, so a method to scale my pictures correct when zooming is VERY appreciated,
Use the new MAPS API V2
Blog article
Video

How can I send Viewport coordinate when using mapview in my android app?

I am using mapview in my android app
using the class com.google.android.maps
I wont lo load markers using background process when the user navigate I want to
send the Viewport coordinate to my server
I can do it in javascript like here
google.maps.event.addListener(map, 'idle', showMarkers);
function showMarkers() {
var bounds = map.getBounds();
// Call you server with ajax passing it the bounds
// In the ajax callback delete the current markers and add new markers
}
But how can I do this in java ? Please suggest.
i post this answer and i hope to save some one else time
i found that the best solution for my case is to use custom map view SimpleMapView
first crete the SimpleMapView class in your project and here is the code
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
public class SimpleMapView extends MapView {
private int currentZoomLevel = -1;
private GeoPoint currentCenter;
private List<ZoomChangeListener> zoomEvents = new ArrayList<ZoomChangeListener>();
private List<PanChangeListener> panEvents = new ArrayList<PanChangeListener>();
public SimpleMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SimpleMapView(Context context, String apiKey) {
super(context, apiKey);
}
public SimpleMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
*
* #return
*/
public int[][] getBounds() {
GeoPoint center = getMapCenter();
int latitudeSpan = getLatitudeSpan();
int longtitudeSpan = getLongitudeSpan();
int[][] bounds = new int[2][2];
bounds[0][0] = center.getLatitudeE6() - (latitudeSpan / 2);
bounds[0][1] = center.getLongitudeE6() - (longtitudeSpan / 2);
bounds[1][0] = center.getLatitudeE6() + (latitudeSpan / 2);
bounds[1][1] = center.getLongitudeE6() + (longtitudeSpan / 2);
return bounds;
}
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint centerGeoPoint = this.getMapCenter();
if (currentCenter == null ||
(currentCenter.getLatitudeE6() != centerGeoPoint.getLatitudeE6()) ||
(currentCenter.getLongitudeE6() != centerGeoPoint.getLongitudeE6()) ) {
firePanEvent(currentCenter, this.getMapCenter());
}
currentCenter = this.getMapCenter();
}
return super.onTouchEvent(ev);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(getZoomLevel() != currentZoomLevel){
fireZoomLevel(currentZoomLevel, getZoomLevel());
currentZoomLevel = getZoomLevel();
}
}
#Override
public void setSatellite(boolean on){
super.setSatellite(on);
}
#Override
public MapController getController(){
return super.getController();
}
private void fireZoomLevel(int old, int current){
for(ZoomChangeListener event : zoomEvents){
event.onZoom(old, current);
}
}
private void firePanEvent(GeoPoint old, GeoPoint current){
for(PanChangeListener event : panEvents){
event.onPan(old, current);
}
}
public void addZoomChangeListener(ZoomChangeListener listener){
this.zoomEvents.add(listener);
}
public void addPanChangeListener(PanChangeListener listener){
this.panEvents.add(listener);
}
}
and in your mapactivity just make
SimpleMapView mapView = (SimpleMapView) findViewById(R.id.mapView);
and then you have
mapView.addPanChangeListener(new PanChangeListener() {
#Override
public void onPan(GeoPoint old, GeoPoint current) {
//TODO
//do your work here
}
});
and add the PanChangeListener class here the code
package yourPkageName;
import com.google.android.maps.GeoPoint;
public interface PanChangeListener {
public void onPan(GeoPoint old, GeoPoint current);
}
and add the ZoomChangeListener class here the code
package yourPkageName;
public interface ZoomChangeListener {
public void onZoom(int old, int current);
}
and in your xml file add
<?xml version="1.0" encoding="utf-8"?>
<YourPakageName.SimpleMapView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:apiKey="0mAbU5bZyFY2I46PFJ1ysXGcYlAmFM6fYBWSB7Q"
android:clickable="true" />

Categories

Resources