{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
I am a person who needs many many multiple alarms to wake up early in the morning.
Every clock/alarm applications I tried has no feature that turning on or off all alarms at once.
So I decided to customize google's official android clock app.
Let's get it started.
### Needed #####
google's clock app source (I use Lollipop's source for my device)
buttons icon image
### Steps #####
1) download source from googlesource.com repository
android system clock application's package name is "DeskClock".
You can clone the source like below
Code:
git clone -b lollipop-release https://android.googlesource.com/platform/packages/apps/DeskClock
2. import it & remove errors
When you import the project to your IDE, you will get thousand of import errors.
Check below
SDK Version
- Make sure that you have lollipop sdk and check "Build Target" in "Project Properties"
AndroidManifest
- Specify min/targetSdkVersion, if it is not in AndroidManifest.xml
- There is "DEVICE_POWER" permission because it is android system app but we can't build system application so just remove it. (In my usage, it seems to be no problem)
Library
- Put android support-library jar file and add it to project build path.
3. Add button icons
- add ic_all_on.png, ic_all_off.png as drawable
4. Edit the sources
There is "DeskClockFragmet" class which is parent of alam/timer/stopwatch/clock's Fragment class.
It uses common layout "desk_clock.xml". It has three buttons (Fab, sub buttons) in the bottom of the screen and Alarm Fragment does not use and just hide sub buttons.
Which means we can easily add buttons to that place.
setLeftRightButtonAppearance() in AlarmClockFragment.java
Code:
@Override
public void setLeftRightButtonAppearance() {
final DeskClock activity = (DeskClock) getActivity();
if (mLeftButton == null || mRightButton == null ||
activity.getSelectedTab() != DeskClock.ALARM_TAB_INDEX) {
return;
}
[COLOR="SeaGreen"] // mLeftButton.setVisibility(View.INVISIBLE);
//mRightButton.setVisibility(View.INVISIBLE);
mLeftButton.setEnabled(true);
mRightButton.setEnabled(true);
mLeftButton.setVisibility(View.VISIBLE);
mRightButton.setVisibility(View.VISIBLE);
mLeftButton.setImageResource(R.drawable.ic_all_off);
mRightButton.setImageResource(R.drawable.ic_all_on);[/COLOR]
}
set mLeft/RightButton enabled and change visibilty to VISIBLE, set button image as drawable we added.
Override onLeftButtonClick/onRightButtonClick() from parent (DeskClockFragment.class) and call switchAlarms() what we will implement.
Code:
@Override
public void onLeftButtonClick(View view) {
super.onLeftButtonClick(view);
mAdapter.switchAlarms(false);
Toast.makeText(getActivity(), "Every Alarms Disabled!!", Toast.LENGTH_SHORT).show();
}
@Override
public void onRightButtonClick(View view) {
super.onRightButtonClick(view);
mAdapter.switchAlarms(true);
Toast.makeText(getActivity(), "Every Alarms Enabled!!", Toast.LENGTH_SHORT).show();
}
+ I just hide menu button in bottom-right just in alarm tab because right and menu button are overlayed.
onCreateView() in AlarmClockFragment.java
Code:
if (menuButton != null) {
// if (isLandscape) {
// menuButton.setVisibility(View.GONE);
// } else {
// menuButton.setVisibility(View.VISIBLE);
// setupFakeOverflowMenuButton(menuButton);
// }
//remove menu button
menuButton.setVisibility(View.GONE);
}
There is AlarmItemAdapter sub class in AlarmClockFragment.java which extends CursorAdapter.
We have to implement something that change actual alarm data in inner database and change views depend on each alarm's on/off value.
I just added a method "swichAlarms" into this sub class to do that.
boolean enabled value is passed as parameter (on : true / off : false)
Get every alarm's view and change the view when the alarm on/off status has to be changed
Also there's a way to get alarms llist in Alarm.java that is getAlarms() method.
I use it to get alarms list a alarm's enabled value is different.
And change alarms enabled value with asyncUpdateAlarm() method in AlarmClockFragment.java
That's all!
easy?
Code:
// added by donghe
/**
* A convenience method to enable or disable every alarm.
* Get child view (visible view) in alarm list and changing ItemAlpha whether if its enabled value is different
* Get alarm(s) linked list from Alarms Class only if the alarm has to be updated.
* Do asynchronous updating.
* @param enabled true for turning on, false for turning off
*/
private void switchAlarms(boolean enabled) {
int totalLength = mList.getCount();
for (int i = 0; i < totalLength; i++) {
View v = mList.getChildAt(i);
if (v != null) {
ItemHolder h = (ItemHolder)(v.getTag());
if (h != null && h.alarm.enabled != enabled) {
setDigitalTimeAlpha(h, enabled);
}
}
}
String selection = null;
if(enabled){
selection = "ENABLED = 0";
}
else{
selection = "ENABLED = 1";
}
ContentResolver cr = getActivity().getApplicationContext().getContentResolver();
List<Alarm> alarmList = Alarm.getAlarms(cr, selection, null);
for(Alarm alarm: alarmList){
alarm.enabled = enabled;
asyncUpdateAlarm(alarm, false);
}
}
Now I feel comfortable to sleep lately in the evening and wake up early in the morning
Hope this thread is helpful to someone like me.
I attached project archive and apk.
Enjoy it!
Thanks men!
Related
I make a android application with a button where in there a total of 4 texts and I want to align the first 2. One at the most left side of the bottom text and the other and the right side of the bottom text.
So from this:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Code:
setText(item.title + " " + item.roomId + "\n" + item.teacher + " " + item.classes);
To this:
This is my class where I extend Button:
Code:
/**
* Custom view that represents a {@link ScheduleItem} instance, including its
* title and time span that it occupies. Usually organized automatically by
* {@link ScheduleItemsLayout} to match up against a {@link TimeRulerView}
* instance.
*/
public class ScheduleItemView extends Button {
private ScheduleItem mItem;
public ScheduleItemView(Context context, ScheduleItem item) {
super(context);
mItem = item;
setSingleLine(false);
setText(item.title + " " + item.roomId + "\n" + item.teacher + " "
+ item.classes);
// TODO: turn into color state list with layers?
int textColor = Color.WHITE;
int accentColor = item.accentColor;
LayerDrawable buttonDrawable = (LayerDrawable) context.getResources()
.getDrawable(R.drawable.btn_block);
buttonDrawable.getDrawable(0).setColorFilter(accentColor,
PorterDuff.Mode.SRC_ATOP);
buttonDrawable.getDrawable(1).setAlpha(item.containsStarred ? 255 : 0);
setTextColor(textColor);
setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources()
.getDimensionPixelSize(R.dimen.text_size_small));
setGravity(Gravity.CENTER | Gravity.BOTTOM);
setBackgroundDrawable(buttonDrawable);
}
public ScheduleItem getScheduleItem() {
return mItem;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY));
// layout(getLeft(), getTop(), getRight(), getBottom());
setGravity(Gravity.CENTER);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getRight() - getLeft(), getBottom() - getTop());
}
}
What I've tried is instead of extending Button, extending RelativeLayout:
Code:
/**
* Custom view that represents a {@link ScheduleItem} instance, including its
* title and time span that it occupies. Usually organized automatically by
* {@link ScheduleItemsLayout} to match up against a {@link TimeRulerView}
* instance.
*/
public class ScheduleItemView extends RelativeLayout {
private ScheduleItem mItem;
public ScheduleItemView(Context context, ScheduleItem item) {
super(context);
mItem = item;
// TODO: turn into color state list with layers?
int textColor = Color.WHITE;
int accentColor = item.accentColor;
LayerDrawable buttonDrawable = (LayerDrawable) context.getResources()
.getDrawable(R.drawable.btn_block);
buttonDrawable.getDrawable(0).setColorFilter(accentColor,
PorterDuff.Mode.SRC_ATOP);
buttonDrawable.getDrawable(1).setAlpha(item.containsStarred ? 255 : 0);
// Three TextViews to hold the `title`, `roomId`
// and `teacher&room` independently
TextView tvTitle = new TextView(context);
TextView tvRoomId = new TextView(context);
TextView tvTeacherAndClasses = new TextView(context);
// Example ids
tvTitle.setId(100);
tvRoomId.setId(101);
tvTeacherAndClasses.setId(102);
tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources()
.getDimensionPixelSize(R.dimen.text_size_small));
tvRoomId.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources()
.getDimensionPixelSize(R.dimen.text_size_small));
tvTeacherAndClasses.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.text_size_small));
tvTitle.setPadding(30, 20, 30, 0);
tvRoomId.setPadding(30, 20, 30, 0);
tvTeacherAndClasses.setPadding(30, 5, 30, 20);
tvTitle.setTextColor(textColor);
tvRoomId.setTextColor(textColor);
tvTeacherAndClasses.setTextColor(textColor);
// Set text
tvTitle.setText(item.title);
tvRoomId.setText(item.roomId);
tvTeacherAndClasses.setText(item.teacher + " " + item.classes);
// LayoutParms
RelativeLayout.LayoutParams paramsTitle = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
paramsTitle.addRule(RelativeLayout.ALIGN_LEFT,
tvTeacherAndClasses.getId());
RelativeLayout.LayoutParams paramsRoomId = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
paramsRoomId.addRule(RelativeLayout.ALIGN_RIGHT,
tvTeacherAndClasses.getId());
RelativeLayout.LayoutParams paramsTeacherAndClasses = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
paramsTeacherAndClasses.addRule(RelativeLayout.CENTER_HORIZONTAL);
paramsTeacherAndClasses.addRule(RelativeLayout.BELOW, tvTitle.getId());
// Add Views to this RelativeLayout
addView(tvTitle, paramsTitle);
addView(tvRoomId, paramsRoomId);
addView(tvTeacherAndClasses, paramsTeacherAndClasses);
// setGravity(Gravity.CENTER | Gravity.BOTTOM);
// Set the background as LayerDrawable
setBackgroundDrawable(buttonDrawable);
}
public ScheduleItem getScheduleItem() {
return mItem;
}
But then only the LayerDrawable is displaying, the text is not displaying. What I've done wrong? I hope that someone can help me with this, the guys on StackOverflow have point me in a good direction with the above code but it's still not working.
The source of the project can be downloaded here (which you can import in Eclipse):
http://we.tl/5PnmaZKXvL
RelativeLayout should be a good idea!
Have you debuged the app? What is the text of the TextViews? The text-size? Why do you set the ids for the TextViews?
Regards
EmptinessFiller said:
RelativeLayout should be a good idea!
Have you debuged the app? What is the text of the TextViews? The text-size? Why do you set the ids for the TextViews?
Regards
Click to expand...
Click to collapse
I've debugged the app, this is what I've tried:
Log the id's of tvTitle, tvRoomId, and tvTeacherAndClasses with getId() to see if the id's is set. (The id's are set in order to get the addRule working)
I've tried to comment these lines:
tvTitle.setId(100);
tvRoomId.setId(101);
tvTeacherAndClasses.setId(102);
And the following as well:
paramsTitle.addRule(RelativeLayout.ALIGN_LEFT, tvTeacherAndClasses.getId());
paramsRoomId.addRule(RelativeLayout.ALIGN_RIGHT, tvTeacherAndClasses.getId());
paramsTeacherAndClasses.addRule(RelativeLayout.BELOW, tvTitle.getId());
If the ids were the problem, the text should show, but the three textviews would overlap now. But I still see no text.
I've tried to give the TextView a color with tvTitle.setBackgroundColor(Color.GREEN); but I still see no text neither a color.
I've tried to comment out setBackgroundDrawable(buttonDrawable); to see if the text's show up. But then I see no text and no button.
I've also tried to use bringToFront() but neither the text is not showing
As last I've tried to use a concrete value for the TextSize with tvTitle.setTextSize(25) but the text is unfortunately not showing.
What could be the problems:
I use Android-ViewPagerIndicator and ViewPager this could be a problem. (Maybe with restoring the view state or multiple id's).
I've only switched a Button to RelativeLayout, I really don't see why problems being introduced in that transition. Does someone know what could be the problem?
Anyone? I've updated the post with a new download link.
You could call getPaint on the button instance and with measuretext you could get the size of the spaces to calculate the count of spaces for the specific size of the button
Just an idea, no idea how it will work in practice
Tapatalked...
Hello,
I create that thread to present you a tutorial learning to draw an Analog Clock on Android with the Canvas 2D API. It is also a good introduction to Canvas API on Android. You can enjoy this tutorial in video on Youtube too :
To learn to use the Canvas 2D API, a good exercise is to draw an Analog Clock on Android. In this tutorial, you are going to discover how to create this kind of Clock and you will see some basics like creating a custom view and draw on a Canvas.
The first step of the tutorial is to create a custom ClockView which will be used to draw the Analog Clock on the screen. To start, you need to define an initClock() method where you get the height and the width of the custom view. The minimum between the height and the width will be used to determine the dimensions of the square nesting the Analog Clock.
For the Analog Clock, we will define the radius by dividing the min between the height and the width by two. We subtract a padding value used to add some padding for the Analog Clock.
To draw the Analog Clock, we need to override the onDraw() method of our ClockView. If the view is not initialized, we call the initClock(). Then, we call the methods defined to draw the circle of the clock, the center, the numerals and the hands.
To update the time displayed on the Clock, we need to invalidate the view. Thus, it is drawn each 500 milliseconds.
It gives us the following code for the ClockView :
Code:
package com.ssaurel.canvasclock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import java.util.Calendar;
public class ClockView extends View {
private int height, width = 0;
private int padding = 0;
private int fontSize = 0;
private int numeralSpacing = 0;
private int handTruncation, hourHandTruncation = 0;
private int radius = 0;
private Paint paint;
private boolean isInit;
private int[] numbers = {1,2,3,4,5,6,7,8,9,10,11,12};
private Rect rect = new Rect();
public ClockView(Context context) {
super(context);
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initClock() {
height = getHeight();
width = getWidth();
padding = numeralSpacing + 50;
fontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 13,
getResources().getDisplayMetrics());
int min = Math.min(height, width);
radius = min / 2 - padding;
handTruncation = min / 20;
hourHandTruncation = min / 7;
paint = new Paint();
isInit = true;
}
@Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
initClock();
}
canvas.drawColor(Color.BLACK);
drawCircle(canvas);
drawCenter(canvas);
drawNumeral(canvas);
drawHands(canvas);
postInvalidateDelayed(500);
}
private void drawHand(Canvas canvas, double loc, boolean isHour) {
double angle = Math.PI * loc / 30 - Math.PI / 2;
int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;
canvas.drawLine(width / 2, height / 2,
(float) (width / 2 + Math.cos(angle) * handRadius),
(float) (height / 2 + Math.sin(angle) * handRadius),
paint);
}
private void drawHands(Canvas canvas) {
Calendar c = Calendar.getInstance();
float hour = c.get(Calendar.HOUR_OF_DAY);
hour = hour > 12 ? hour - 12 : hour;
drawHand(canvas, (hour + c.get(Calendar.MINUTE) / 60) * 5f, true);
drawHand(canvas, c.get(Calendar.MINUTE), false);
drawHand(canvas, c.get(Calendar.SECOND), false);
}
private void drawNumeral(Canvas canvas) {
paint.setTextSize(fontSize);
for (int number : numbers) {
String tmp = String.valueOf(number);
paint.getTextBounds(tmp, 0, tmp.length(), rect);
double angle = Math.PI / 6 * (number - 3);
int x = (int) (width / 2 + Math.cos(angle) * radius - rect.width() / 2);
int y = (int) (height / 2 + Math.sin(angle) * radius + rect.height() / 2);
canvas.drawText(tmp, x, y, paint);
}
}
private void drawCenter(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, 12, paint);
}
private void drawCircle(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.white));
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
canvas.drawCircle(width / 2, height / 2, radius + padding - 10, paint);
}
}
Once the ClockView is created, we have just to add it in the layout of our main activity :
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ssaurel.canvasclock.MainActivity"
android:background="@android:color/black"
>
<com.ssaurel.canvasclock.ClockView
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true"/>
</RelativeLayout>
The code of the Main Activity is simple. We have just to set this layout as the content view :
Code:
package com.ssaurel.canvasclock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Now, you can try your Analog Clock application and you should enjoy the following result :
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Don't hesitate to give it a try and give me your feedbacks.
Thanks.
Sylvain
thank you soo much
Hello,
I create that thread to present you a tutorial learning you to create the game "Spin The Bottle" for Android. It is also a good way to discover how to use Android Animations API.
What is better that creating some funny games to learn the basics of Android development ? Today, you’re going to discover more on the Android Animation API by creating the game “Spin The Bottle”.
Note that you can discover this tutorial in video on Youtube too :
Spin the bottle is a party game in which several players sit/stand/kneel in a circle. A bottle is placed on the floor in the center of the circle. A player spins the bottle, and must kiss the person to whom the bottle points when it stops spinning.
With our application, it is not necessary any more to have a real bottle, you will just need a smartphone. And it’s great because everyone has always a smartphone on him nowadays.
First step is to find two specific images : one for the floor which will be defined as background of the game and an other for the bottle which will be rotated during the game.
We have chosen to use these both images :
Floor image for the background
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Bottle image
Now, we need to define the layout of our game “Spin The Bottle”. We have a Relative Layout with the floor image as background and we add a bottle in the center of this layout :
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background">
<ImageView
android:id="@+id/bottle"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/beer_bottle"
android:layout_centerInParent="true" />
</RelativeLayout>
The next step is to write the Java code of the Main Activity. First, we get references for the views. Then, we install a click listener on the main layout of the Main Activity. Thus, the user will just have to touch the screen to spin the bottle and to start the game.
The core of the game is in the spinTheBottle() method. We are going to define a RotateAnimation from start angle to end angle centered on the center of the bottle. The end angle will be defined randomly to simulate really the game “Spin The Bottle”. We should store the last end angle value to restart the bottle in the same position for the next turn of the game.
The Code of the Main Activity will have the following form :
Code:
package com.ssaurel.spinthebottle;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
public static final Random RANDOM = new Random();
private View main;
private ImageView bottle;
private int lastAngle = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
main = findViewById(R.id.root);
bottle = (ImageView) findViewById(R.id.bottle);
main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
spinTheBottle();
}
});
Toast.makeText(this, R.string.touch_to_spin, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.action_spin :
spinTheBottle();
break;
case R.id.action_zero :
resetTheBottle();
break;
}
return super.onOptionsItemSelected(item);
}
private void spinTheBottle() {
int angle = RANDOM.nextInt(3600 - 360) + 360;
float pivotX = bottle.getWidth() / 2;
float pivotY = bottle.getHeight() / 2;
final Animation animRotate = new RotateAnimation(lastAngle == -1 ? 0 : lastAngle, angle, pivotX, pivotY);
lastAngle = angle;
animRotate.setDuration(2500);
animRotate.setFillAfter(true);
bottle.startAnimation(animRotate);
}
private void resetTheBottle() {
float pivotX = bottle.getWidth() / 2;
float pivotY = bottle.getHeight() / 2;
final Animation animRotate = new RotateAnimation(lastAngle == -1 ? 0 : lastAngle, 0, pivotX, pivotY);
lastAngle = -1;
animRotate.setDuration(2000);
animRotate.setFillAfter(true);
bottle.startAnimation(animRotate);
}
}
You can run the application and play your game :
To go further, you can download the game “Spin The Bottle” created in this tutorial on the Google Play Store :
https://play.google.com/store/apps/details?id=com.ssaurel.spinthebottle
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Picture in Picture Mode (PinP) introduced on Android 8 (API 26) for All Android devices.
Its most used functionality is to shoa w preview of some data like Video Players, Maps & etc.
But there is also an option to add Custom Action & with this, you can do whatever you want to do.
This is Tutorial is about How to Add PinP for an Activity & Implementing Custom Actions to Create Shortcuts of Other Apps & Open Them
Click to expand...
Click to collapse
# Let Start #
What do you need to get started...
- Know How to Create Hello World Project
- Basic Info about Android Apps Files (.Java, .xml)/Components (Activity, Service)
Click to expand...
Click to collapse
What will you learn...
- Configure an Activity for PinP Mode
- Create PendingIntent
- Load Apps Info from PackageManager (AppName, AdaptiveIcons & etc)
- Load File Data in Lines
Click to expand...
Click to collapse
- Create new Android Studio Project, then open AndroidManifest.xml to add PinP attributes.
(I suggest to create specific Activity to handle PinP)
Code:
<activity
android:name=".PictureInPicture.PinP"
android:label="@string/title_activity_pin_p"
[B][COLOR="Blue"]android:resizeableActivity="true"
android:supportsPictureInPicture="true"[/COLOR][/B]
android:theme="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar" />
<!-- I used Wallpaper theme cause in my case I want to have transparent background that shows home launcher wallpaper -->
- Now Open your PinP Activity, PinP.java
The layout your design for this activity will show inside PinP Window.
(Here I set 3 ImageViews to load icons of selected apps that I load from a saved file)
To enter PinP Mode your need only to call this activity.enterPictureInPictureMode(); But you should add some functionality to it. READ COMMENTS IN CODE
Code:
public void enterPinP(){
[COLOR="Red"]//load packageNames from saved file[/COLOR]
List<String> appShortcutsPackageName = Arrays.asList(functionsClass.readFileLine(".autoSuper"));
[COLOR="red"]//set icon of apps to imageView[/COLOR]
imageViewOne.setImageBitmap(appIconBitmap(appShortcutsPackageName.get(0)));
imageViewTwo.setImageBitmap(appIconBitmap(appShortcutsPackageName.get(1)));
imageViewThree.setImageBitmap(appIconBitmap(appShortcutsPackageName.get(2)));
[COLOR="red"]//get main intent of apps[/COLOR]
Intent intentOne = getPackageManager().getLaunchIntentForPackage(appShortcutsPackageName.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent intentTwo = getPackageManager().getLaunchIntentForPackage(appShortcutsPackageName.get(1)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent intentThree = getPackageManager().getLaunchIntentForPackage(appShortcutsPackageName.get(2)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
[COLOR="red"]//create pending intent for actions on PinP Window [/COLOR]
[COLOR="red"]//you can also [b]getService()[/b] & [b]getBroadcast()[/b] [/COLOR]
final PendingIntent pendingIntentOne = PendingIntent.getActivity(getApplicationContext(), 333, intentOne,0);
final PendingIntent pendingIntentTwo = PendingIntent.getActivity(getApplicationContext(), 666, intentTwo,0);
final PendingIntent pendingIntentThree = PendingIntent.getActivity(getApplicationContext(), 999, intentThree,0);
[COLOR="red"]//here I add icon from resource to indicate where to click on PinP Window[/COLOR]
final Icon oneIcon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_click);
final Icon twoIcon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_click);
final Icon threeIcon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_click);
[COLOR="red"]//remote actions to handle click from PinP Window[/COLOR]
final ArrayList<RemoteAction> remoteActions = new ArrayList<>();
remoteActions.add(new RemoteAction(oneIcon, appName(appShortcutsPackageName.get(0)), appName(appShortcutsPackageName.get(0)), pendingIntentOne));
remoteActions.add(new RemoteAction(twoIcon, appName(appShortcutsPackageName.get(1)), appName(appShortcutsPackageName.get(1)), pendingIntentTwo));
remoteActions.add(new RemoteAction(threeIcon, appName(appShortcutsPackageName.get(2)), appName(appShortcutsPackageName.get(2)), pendingIntentThree));
//
final PictureInPictureParams.Builder pictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
pictureInPictureParamsBuilder.setActions(remoteActions);
pictureInPictureParamsBuilder.setAspectRatio(new Rational(50, 100));
PictureInPictureParams pictureInPictureParams = pictureInPictureParamsBuilder.build();
[B][COLOR="red"]//This is Main Functions. That you can Call from any Activity [/COLOR]
[COLOR="RoyalBlue"]enterPictureInPictureMode(pictureInPictureParams);[/COLOR][/B]
}
So wherever you call this function, it will enter to Picture in Picture Mode.
For Example, when click a button or @onPause() to continue showing some data.
I call it from onCreate(Bundle bundle) cause I create an activity specific for PinP Mode.
Also, you can recognize whenever your activity enter full screen mode Or PinP by overriding this functions
It is good way to add/remove views If you are using same activity for both full screen and PinP mode.
Code:
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, configuration);
if (isInPictureInPictureMode){
//picture in picture mode
}
else {
//full screen mode
}
}
Note: Limited amount of RemoteActions can be set for PinP Mode that you can get it from activity.getMaxNumPictureInPictureActions();
Oo. Download | PinP Shortcuts .oO
Super Shortcuts | Available in Beta Stage
Feel Free to Ask for Promo Code
Thanks for Support my ProjectsDon't forget to Hit Thanks
PinP Shortcuts | Extra Functions
Extra Functions Used in Tutorial
Code:
public Bitmap [B]appIconBitmap[/B](String pack){
Bitmap bitmap = null;
try{
Drawable drawableIcon = context.getPackageManager().getApplicationIcon(pack);
if (drawableIcon instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable)drawableIcon).getBitmap();
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (drawableIcon instanceof AdaptiveIconDrawable){
Drawable backgroundDrawable = ((AdaptiveIconDrawable) drawableIcon).getBackground();
Drawable foregroundDrawable = ((AdaptiveIconDrawable) drawableIcon).getForeground();
Drawable[] drawables = new Drawable[2];
drawables[0] = backgroundDrawable;
drawables[1] = foregroundDrawable;
LayerDrawable layerDrawable = new LayerDrawable(drawables);
int width = layerDrawable.getIntrinsicWidth();
int height = layerDrawable.getIntrinsicHeight();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);
}
else{
bitmap = Bitmap.createBitmap(drawableIcon.getIntrinsicWidth(), drawableIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
}
else{
bitmap = Bitmap.createBitmap(drawableIcon.getIntrinsicWidth(), drawableIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
}
catch(Exception e){
e.printStackTrace();
}
return bitmap;
}
Code:
public String [B]appName[/B](String pack){
String Name = null;
try{
PackageManager packManager = context.getPackageManager();
ApplicationInfo app = context.getPackageManager().getApplicationInfo(pack, 0);
Name = packManager.getApplicationLabel(app).toString();
}
catch(Exception e){
e.printStackTrace();
}
return Name;
}
Thanks for Support my ProjectsDon't forget to Hit Thanks
Reserved
More articles like this, you can visit HUAWEI Developer Forum and Medium.
Forum link: https://forums.developer.huawei.com/forumPortal/en/home
Medium link: https://medium.com/huawei-developers
It is a tradition to exchange business cards with new colleges or partners, but keeping physical business cards is not easy at all. To solve this problem, many apps and mini programs providing the electronic business card function have emerged. You must be wondering how to develop such a function for your app.
Try integrating HUAWEI Nearby Service and use its Nearby Message feature to quickly implement the point-to-point business card exchange function. Check out the function demo below.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
If you are interested in the implementation details, download the source code from GitHub. You can optimize the code based on your app requirements.
Github demo link: https://github.com/HMS-Core/hms-nearby-demo/tree/master/NearbyCardExchange
The development procedure is as follows:
1. Getting Started
If you are already a Huawei developer, skip this step. If you are new to Huawei Mobile Services (HMS), you need to configure app information in AppGallery Connect, enable Nearby Service on the HUAWEI Developers console, and integrate the HMS Core SDK. For details, please refer to the documentation.
2. Adding Permissions
Before using Nearby Message, add the network, Bluetooth, and location permissions. Add the following permissions to the AndroidManifest.xml file of your project:
Code:
<uses-permission android:name="android.permission.INTERNET " />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- The location permission is also required in Android 6.0 or later. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
1. Code Development
2.1 Submitting a Dynamic Permission Application
Ensure that the Bluetooth and location functions are enabled and the device has been connected to the Internet properly. Then submit a dynamic permission application for the location permission.
Code:
@Override
public void onStart() {
super.onStart();
getActivity().getApplication().registerActivityLifecycleCallbacks(this);
checkPermission();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
for (int i = 0; i < permissions.length; ++i) {
if (grantResults[i] != 0) {
showWarnDialog(Constants.LOCATION_ERROR);
}
}
}
private void checkPermission() {
if (!BluetoothCheckUtil.isBlueEnabled()) {
showWarnDialog(Constants.BLUETOOTH_ERROR);
return;
}
if (!LocationCheckUtil.isLocationEnabled(this.getActivity())) {
showWarnDialog(Constants.LOCATION_SWITCH_ERROR);
return;
}
if (!NetCheckUtil.isNetworkAvailable(this.getActivity())) {
showWarnDialog(Constants.NETWORK_ERROR);
return;
}
String[] deniedPermission = PermissionUtil.getDeniedPermissions(this.getActivity(), new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
});
if (deniedPermission.length > 0) {
PermissionUtil.requestPermissions(this.getActivity(), deniedPermission, 10);
}
}
2.2 Encapsulating the Business Card Publishing and Subscription APIs
When a subscribed business card message is detected by the onFound method, display it in the business card searching pop-up; when a business card message is no longer discoverable (onLost), delete it from the business card searching pop-up.
Code:
private MessageHandler mMessageHandler = new MessageHandler() {
@Override
public void onFound(Message message) {
CardInfo cardInfo = JsonUtils.json2Object(new String(message.getContent(), Charset.forName("UTF-8")),
CardInfo.class);
if (cardInfo == null) {
return;
}
mSearchCardDialogFragment.addCardInfo(cardInfo);
}
@Override
public void onLost(Message message) {
CardInfo cardInfo = JsonUtils.json2Object(new String(message.getContent(), Charset.forName("UTF-8")),
CardInfo.class);
if (cardInfo == null) {
return;
}
mSearchCardDialogFragment.removeCardInfo(cardInfo);
}
};
private void publish(String namespace, String type, int ttlSeconds, OnCompleteListener<Void> listener) {
Message message = new Message(JsonUtils.object2Json(mCardInfo).getBytes(Charset.forName("UTF-8")), type,
namespace);
Policy policy = new Policy.Builder().setTtlSeconds(ttlSeconds).build();
PutOption option = new PutOption.Builder().setPolicy(policy).build();
Nearby.getMessageEngine(getActivity()).put(message, option).addOnCompleteListener(listener);
}
private void subscribe(String namespace, String type, int ttlSeconds, OnCompleteListener<Void> listener,
GetCallback callback) {
Policy policy = new Policy.Builder().setTtlSeconds(ttlSeconds).build();
MessagePicker picker = new MessagePicker.Builder().includeNamespaceType(namespace, type).build();
GetOption.Builder builder = new GetOption.Builder().setPolicy(policy).setPicker(picker);
if (callback != null) {
builder.setCallback(callback);
}
Nearby.getMessageEngine(getActivity()).get(mMessageHandler, builder.build()).addOnCompleteListener(listener);
}
2.3 Processing the Business Card Exchange Menu
When two users exchange business cards face to face, exchange the business card exchange codes. When the business card message of the remote endpoint is published, subscribe to it.
Code:
private boolean onExchangeItemSelected() {
PinCodeDialogFragment dialogFragment = new PinCodeDialogFragment(passwrod -> {
MyCardFragment.this.publish(passwrod, passwrod, Policy.POLICY_TTL_SECONDS_MAX, result -> {
if (!result.isSuccessful()) {
String str = "Exchange card fail, because publish my card fail. exception: "
+ result.getException().getMessage();
Log.e(TAG, str);
Toast.makeText(getActivity(), str, Toast.LENGTH_LONG).show();
return;
}
MyCardFragment.this.subscribe(passwrod, passwrod, Policy.POLICY_TTL_SECONDS_INFINITE, ret -> {
if (!ret.isSuccessful()) {
MyCardFragment.this.unpublish(passwrod, passwrod, task -> {
String str = "Exchange card fail, because subscribe is fail, exception("
+ ret.getException().getMessage() + ")";
if (!task.isSuccessful()) {
str = str + " and unpublish fail, exception(" + task.getException().getMessage()
+ ")";
}
Log.e(TAG, str);
Toast.makeText(getActivity(), str, Toast.LENGTH_LONG).show();
});
return;
}
mSearchCardDialogFragment.setOnCloseListener(() -> {
MyCardFragment.this.unpublish(passwrod, passwrod, task -> {
if (!task.isSuccessful()) {
Toast.makeText(getActivity(), "Unpublish my card fail, exception: "
+ task.getException().getMessage(), Toast.LENGTH_LONG).show();
}
});
MyCardFragment.this.unsubscribe(task -> {
if (!task.isSuccessful()) {
Toast.makeText(getActivity(), "Unsubscribe fail, exception: "
+ task.getException().getMessage(), Toast.LENGTH_LONG).show();
}
});
});
mSearchCardDialogFragment.show(getParentFragmentManager(), "Search Card");
}, null);
});
});
dialogFragment.show(getParentFragmentManager(), "pin code");
return true;
}
2.4 Adding a Business Card to Favorites
When a user adds a business card to favorites, add the card to the favorites list; when a user removes a business card from favorites, remote the card from the favorites list. In addition, store related data locally.
Code:
@Override
public void onFavorite(CardInfo cardInfo, boolean isFavorite) {
if (isFavorite) {
mFavoriteMap.put(cardInfo.getId(), cardInfo);
} else {
mFavoriteMap.remove(cardInfo.getId());
}
Set<String> set = new HashSet<>(mFavoriteMap.size());
for (CardInfo card : mFavoriteMap.values()) {
set.add(JsonUtils.object2Json(card));
}
SharedPreferences sharedPreferences = getContext().getSharedPreferences("data", Context.MODE_PRIVATE);
sharedPreferences.edit().putStringSet(Constants.MY_FAVORITES_KEY, set).apply();
}
5. Conclusion
This demo uses Nearby Message feature of HUAWEI Nearby Service. What Nearby Message is capable of is more than just developing functions for exchanging business cards face-to-face. Here are some examples:
1. Face-to-face teaming in multiplayer sports games
2. Face-to-face round joining in board games
3. Near-field go-Dutch payment function
4. Music sharing
If you are interested and want to learn more, check our development guide at
https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/nearby-service-introduction
Find more Huawei HMS development guides in the XDA HMS community forums.