This tutorial explains how to create a widget taking location widget as an example. It also shows how to create widget configuration or settings option.
To know the basic concepts of widgets and how to create simple widget, see android widgets basics tutorial.
The example widget displays current location name. Current location name based on the device location is obtained using Google places SDK for Android.
When widget needs to refresh the data as per widget configuration, it creates a background job which fetches current place name and updates the widget with current place name.
When widget is placed on the screen (when it is created), settings screen is displayed. Setting screen allows users to set background opacity and text colors. User can also get to settings screen by clicking the widget.
The app which contains the location widget has a screen which also displays current location. The app uses RxJava to get the location data and display in UI.
Location name widget uses Google places sdk for android, RxJava and workmanager. So, add those libraries to module build.grale file.
implementation 'android.arch.work:work-runtime:1.0.0-rc02'
implementation 'com.google.android.libraries.places:places:1.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.google.guava:guava:27.0.1-android'
To use Google places API, you need to obtain key, enable billing and enable places API. Follow the instructions provided here.
The activity just displays location name in a text view. The activity prompts for android.permission.ACCESS_FINE_LOCATION permission if it is not granted as the permission is required for calling Google places API.
Google places API is called using RxJava from the app so that API call can run in the background thread and results can be updated in UI on the main thread once the place response is received. Place name is store in shared preferences.
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loactionName = findViewById(R.id.location_name);
//google places api requires location access permission
checkPermission(ACCESS_FINE_LOCATION);
getPlaces();
}
}
Since widget configuration activity also uses permission function and the code related to Google places API, the common code is added to BaseActivity class which is extended by both main activity and widget configuration activity classes.
In the base activity on create method, location data API and shared preferences objects are instantiated. It contains methods to check location permission and to call location API in the background using RxJava.
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
public class BaseActivity extends AppCompatActivity {
protected LocationDataProviderInterface locationDataProvider;
protected TextView loactionName;
protected SharedPreferences sharedPref;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//instance of location provider
// which uses google places api to get location data
locationDataProvider = new LocationDataProvider(this);
//shared preferences used for storing settings
//one setting for all instances of the widget
sharedPref = getApplicationContext().getSharedPreferences(
getString(R.string.location_pref), Context.MODE_PRIVATE);
}
//gets place info in the background using rxjava
protected void getPlaces(){
Observable.just(locationDataProvider.getPlaces()).subscribeOn(Schedulers.computation())
.flatMap(s -> {
while(!s.isComplete()){
Thread.sleep(100);
}
return Observable.just(s.getResult().getPlaceLikelihoods()
.get(0).getPlace().getName());
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResults, this::handleError );
}
protected void handleResults(String place){
loactionName.setText(place);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.place), place);
editor.commit();
}
protected void handleError(Throwable t){
Log.e("place api error", ""+ t.toString());
loactionName.setText("couldn't get place info");
}
protected boolean checkPermission(String permission) {
boolean hasPermission =
ContextCompat.checkSelfPermission(this, permission)
== PackageManager.PERMISSION_GRANTED;
if (!hasPermission) {
ActivityCompat.requestPermissions(this, new String[]{permission}, 0);
}
return hasPermission;
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults) {
if (requestCode == 0) {
int i = 0;
for(String perm : permissions){
if(perm.equals(ACCESS_FINE_LOCATION) &&
grantResults[i] == PackageManager.PERMISSION_GRANTED){
getPlaces();
}
i++;
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
The class which provides widget UI and its functionality is a service and it is created by extending AppWidgetProvider class and overriding onEnabled, onDisabled, onUpdate or onReceive methods as required.
Our location widget provider’s onEnabled method, which is called when widget is instantiated, contains code related to location access permission, if ACCESS_FINE_LOCATION permission is not granted, main activity will be started. The main activity will prompt users asking to allow or deny the permission.
The method which gets called every time the widget needs to be refreshed is onUpdate method. In this method, a work which will call Google places API is scheduled using work manager.
On receiving the response from location API, the task broadcasts a message to the widget provider service. The service’s onReceive method handles the intent. It will retrieve the location data from the intent and refresh the widget with the location name received.
One important behavior that needs to be noticed when using work manager is that it enables and disables components depending on the usage to release resources. In this process work manager sends ACTION_PACKAGE_CHANGED broadcast. In our example after a work is scheduled to call places API, the broadcast will result in a call to onUpdate() of app widget provider creating an infinite loop. To fix this issue, we put a restriction of 5 minutes lapse between API calls. The last time at which the api is called is stored in shared preferences.
When widget is created or refreshed, location name needs to be populated and pending intent which get fired when widget is clicked to show settings screen needs to be added. Since our example provides widget-settings feature, the user selected settings need to be applied to the UI elements of the widget when it is create and updated. All this code related to widget views construction exists in the widget provider class.
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.RemoteViews;
import java.util.Date;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
public class LocationAppWidgetProvider extends AppWidgetProvider {
public static final String DISPLAY_PLACE = "com.zoftino.locationinfowidget.DISPLAY_PLACE";
public static final String WIDGET_CLICK = "com.zoftino.locationinfowidget.WIDGET_CLICK";
public static final String PLACE_NAME = "PLACE_NAME";
public static final String DEFAULT_LOCATION = "Getting Place Name";
@Override
public void onEnabled(Context context) {
LocalBroadcastManager.getInstance(context)
.registerReceiver(this, new IntentFilter(DISPLAY_PLACE));
LocalBroadcastManager.getInstance(context)
.registerReceiver(this, new IntentFilter(WIDGET_CLICK));
//location access permission is required
if (!(ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)) {
Intent mainActivity = new Intent(context, MainActivity.class);
context.startActivity(mainActivity);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
//this logic prevents infinite loop which is caused due to
//the broadcast of ACTION_PACKAGE_CHANGED in the process of
// idle resource cleanup by the work manager
if(isLocationWorkCalledRecently(context.getApplicationContext())){
return;
}
updateLocationWidget(context.getApplicationContext(), null);
//schedule location fetcher job
LocationScheduler.scheduleGetPlacesWork(appWidgetIds);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals(DISPLAY_PLACE)) {
//display place after fetching
String placeName = intent.getStringExtra(PLACE_NAME);
updateLocationWidget(context.getApplicationContext(), placeName);
}else if (intent.getAction().equals(WIDGET_CLICK)){
//start settings activity
Intent settings = new Intent(context, WidgetConfigurationActivity.class);
settings.setAction(WIDGET_CLICK);
context.startActivity(settings);
}
}
//update location widgets
public static void updateLocationWidget(Context context, String placeName){
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId[] = appWidgetManager
.getAppWidgetIds(new ComponentName(context,
LocationAppWidgetProvider.class));
RemoteViews rviews = getRemoteViewsOfWidget(context, appWidgetId, placeName);
appWidgetManager.updateAppWidget(appWidgetId, rviews);
}
private static RemoteViews getRemoteViewsOfWidget(Context context,
int appWidgetId[], String placeName){
RemoteViews rviews = new RemoteViews(context.getPackageName(),
R.layout.location_widget_layout);
//add pending intent to handle widget click events
setWidgetClickPendingIntent(context, appWidgetId, rviews);
//apply current settings
applySettings(context, appWidgetId, rviews);
//display current place
populateData(context, rviews, placeName);
return rviews;
}
//sets pending intent which gets fired on clicking widget
private static void setWidgetClickPendingIntent(Context context, int appWidgetId[],
RemoteViews rviews){
Intent intent = new Intent(context, LocationAppWidgetProvider.class);
intent.setAction(WIDGET_CLICK);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
rviews.setOnClickPendingIntent(R.id.container, pendingIntent);
}
//applies settings to widgets
private static void applySettings(Context context, int appWidgetId[], RemoteViews rviews) {
SharedPreferences sharedPref = context.getSharedPreferences(
context.getString(R.string.location_pref), Context.MODE_PRIVATE);
int widgetBackground = 255 - sharedPref.getInt(
context.getString(R.string.widget_background), 255);
Color bg = Color.valueOf(context.getResources().getColor(R.color.widgetBg, null));
widgetBackground = Color.argb((float)widgetBackground, bg.red(), bg.green(), bg.blue() );
rviews.setInt(R.id.container, "setBackgroundColor", widgetBackground);
int textcolor = sharedPref.getInt(context.getString(R.string.widget_color), R.id.black);
rviews.setTextColor(R.id.location_name, ContextCompat.getColor(context, textcolor));
}
//populates widgets with current place name
private static void populateData(Context context, RemoteViews rviews, String placeName) {
SharedPreferences sharedPref = context.getSharedPreferences(
context.getString(R.string.location_pref), Context.MODE_PRIVATE);
if(placeName == null){
placeName = sharedPref.getString(context.getString(R.string.place), placeName);
}else{
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(context.getString(R.string.place), placeName);
editor.commit();
}
if(placeName == null || placeName.isEmpty()){
placeName = DEFAULT_LOCATION;
}
rviews.setTextViewText(R.id.location_name, placeName);
}
//this is used to prevent loop
//due to work manager ACTION_PACKAGE_CHANGED broadcast
//which results in a call to onUpdate method
private static boolean isLocationWorkCalledRecently(Context context){
SharedPreferences sharedPref = context.getSharedPreferences(
context.getString(R.string.location_pref), Context.MODE_PRIVATE);
long lastApiCallTime = sharedPref.getLong(
context.getString(R.string.last_api_call), 0);
if(lastApiCallTime != 0){
long currentTime = new Date().getTime();
double mins = (currentTime - lastApiCallTime)/60000;
if(mins > 5){
sharedPref.edit().putLong(context.getString(R.string.last_api_call),
currentTime).commit();
return false;
}
}else{
sharedPref.edit().putLong(context.getString(R.string.last_api_call),
new Date().getTime()).commit();
}
return true;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/widgetBg">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
android:textColor="@color/widgetTxt"
android:textSize="20dp"
android:text="Current Location"/>
<TextView
android:id="@+id/location_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
android:textColor="@color/widgetTxt"
android:textSize="20dp"/>
</LinearLayout>
The example uses work manager to schedule a background job which will fetch location name.
public class LocationScheduler {
public static void scheduleGetPlacesWork(int appWidgetId[]) {
Data source = new Data.Builder()
.putIntArray("widgetId", appWidgetId)
.build();
OneTimeWorkRequest workRequest =
new OneTimeWorkRequest.Builder(LocationWork.class)
.setInputData(source)
.build();
WorkManager.getInstance().enqueue(workRequest);
}
}
The work class calls places API to get location information and updates the widget with the results.
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class LocationWork extends Worker {
private Context ctx;
private LocationDataProviderInterface locationDataProvider;
private int appWidgetId[];
public LocationWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
ctx = context;
appWidgetId = workerParams.getInputData().getIntArray("widgetId");
}
@NonNull
@Override
public Worker.Result doWork() {
getPlaces(new LocationDataProvider(ctx), appWidgetId);
return Worker.Result.success();
}
private void getPlaces(LocationDataProvider locationDataProvider, int appWidgetId[]) {
Task<FindCurrentPlaceResponse> placeTask = locationDataProvider.getPlaces();
placeTask.addOnSuccessListener(
(response) -> {
String pname = response.getPlaceLikelihoods().get(0).getPlace().getName();
displayPlaceInWidget(pname, appWidgetId);
});
placeTask.addOnFailureListener(
(exception) -> {
exception.printStackTrace();
displayPlaceInWidget("Couldn't get", appWidgetId);
});
}
private void displayPlaceInWidget(String placeName, int appWidgetId[]) {
//broadcast message to widget provider to update widget with
//the place data received from places API
Intent widgetIntent = new Intent();
widgetIntent.setAction(LocationAppWidgetProvider.DISPLAY_PLACE);
widgetIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
appWidgetId);
widgetIntent.putExtra(LocationAppWidgetProvider.PLACE_NAME,
placeName);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(widgetIntent);
}
}
To update the widget with results from the background job, a broadcast message is sent to the widget provider from the background work. The onReceive method of widget provider updates the widget with the received data from the API, see above code for details.
Following class contains code related to Google places API.
import android.content.Context;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;
import java.util.ArrayList;
import java.util.List;
public class LocationDataProvider implements LocationDataProviderInterface {
private PlacesClient placesClient;
public LocationDataProvider(Context ctx){
if (!Places.isInitialized()) {
String gApiKey = ctx.getString(R.string.g_api_key);
Places.initialize(ctx, gApiKey);
}
placesClient = Places.createClient(ctx);
}
@Override
public Task<FindCurrentPlaceResponse> getPlaces() {
List<Place.Field> fields = new ArrayList<>();
fields.add(Place.Field.NAME);
FindCurrentPlaceRequest currentPlaceRequest =
FindCurrentPlaceRequest.newInstance(fields);
Task<FindCurrentPlaceResponse> currentPlaceTask;
try{
currentPlaceTask =
placesClient.findCurrentPlace(currentPlaceRequest);
}catch (SecurityException e){
return null;
}
return currentPlaceTask;
}
@Override
public void getPlaceDetails() {
}
}
The example shows how to add widget configuration feature. When widget is created, user is presented with widget configuration screen where user can select settings for widget. In the location widget example, widget background transparency and text color are configurable.
To provide widget configuration feature when widget is instantiated, you need to define configuration layout and activity for UI and behavior of widget configuration and then add the activity as a value to android:configure attribute in the widget provider xml definition.
Our example widget configuration UI contains radio buttons for allowing users to select widget text color and SeekBar for allowing users to select widget background transparency. The widget configuration UI also displays widget with changing background transparency and text colors as user selects them on the screen.
On closing the configuration window, all the selected settings will be saved in shared preferences and applied to widget instances.
User can also get to widget configuration screen to change widget setting by clicking the widget. When widget is constructed, pending intent is added to the root view of the widget layout. The intent gets fired when the root view is clicked and the handler will start configuration activity in response to the event.
Intent intent = new Intent(context, LocationAppWidgetProvider.class);
intent.setAction(WIDGET_CLICK);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
rviews.setOnClickPendingIntent(R.id.container, pendingIntent);
The activity is used for configuring location widget when it is instantiated and it is clicked.
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
public class WidgetConfigurationActivity extends BaseActivity {
private LinearLayout container;
private SeekBar seekBar;
private RadioGroup rg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widget_config_layout);
loactionName = findViewById(R.id.location_name);
container = findViewById(R.id.container);
seekBar = findViewById(R.id.widgetBackground);
rg = findViewById(R.id.widgetColor);
//populate setting-UI elements with the selected values or default values
seekBar.setProgress(sharedPref.getInt(getString(R.string.widget_background), 0));
rg.check(sharedPref.getInt(getString(R.string.widget_color_button), R.id.black));
//apply settings to the widget
//helps in viewing how widget looks with current settings.
applySettings();
//handle widget background settings and store in shared preferences
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//save the selected background
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.widget_background), i);
editor.commit();
applySettings();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
//google places api requires location access peremission
checkPermission(ACCESS_FINE_LOCATION);
//get current place data
getPlaces();
}
//handle text color setting and store in shared preferences
public void onChooseColor(View view) {
if (!((RadioButton) view).isChecked()) {
return;
}
int color = android.R.color.black;
int selectedRadio = R.id.black;
switch (view.getId()) {
case R.id.red:
color = android.R.color.holo_red_dark;
selectedRadio = R.id.red;
break;
case R.id.blue:
color = android.R.color.holo_blue_dark;
selectedRadio = R.id.blue;
break;
case R.id.green:
color = android.R.color.holo_green_dark;
selectedRadio = R.id.green;
break;
case R.id.black:
color = android.R.color.black;
selectedRadio = R.id.black;
break;
}
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.widget_color), color);
editor.putInt(getString(R.string.widget_color_button), selectedRadio);
editor.commit();
applySettings();
}
//apply selected settings to the widget to see how widget
//looks with the selected settings.
private void applySettings() {
int widgetBackground = sharedPref.getInt(getString(R.string.widget_background), 0);
//0 means transparent , 255 opaque
//convert value to percentage
int transparentBackground = widgetBackground * 100 / 255;
int textcolor = sharedPref.getInt(getString(R.string.widget_color), android.R.color.black);
container.getBackground().setAlpha(transparentBackground);
loactionName.setTextColor(ContextCompat.getColor(getApplicationContext(), textcolor));
}
//close settings screen
public void closeSettings(View v) {
//update all widgets to apply the slected settings.
LocationAppWidgetProvider.updateLocationWidget(getApplicationContext(), null);
Intent widgetIntent = new Intent();
setResult(RESULT_OK, widgetIntent);
finish();
//System.exit(0);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:orientation="vertical"
tools:context=".WidgetConfigurationActivity">
<TextView
android:id="@+id/widgetTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Location Widget"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<include layout="@layout/location_widget_layout" />
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Location Widget Settings"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RadioGroup
android:id="@+id/widgetColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Color"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<RadioButton
android:id="@+id/red"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onChooseColor"
android:text="Red" />
<RadioButton
android:id="@+id/blue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onChooseColor"
android:text="Blue" />
<RadioButton
android:id="@+id/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onChooseColor"
android:text="Green" />
<RadioButton
android:id="@+id/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onChooseColor"
android:text="Black" />
</RadioGroup>
<TextView
android:id="@+id/title2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Background"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/widgetColor" />
<SeekBar
android:id="@+id/widgetBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="255"
android:min="0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title2" />
<Button
android:id="@+id/close_settings"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="closeSettings"
android:text="Close Settings"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/widgetBackground" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
User is presented with SeekBar in the widget configuration screen for selecting opacity or transparency value from 0 to 255 for widget background. User selected opacity values will be used to create widget background color using argb() function of Color. Thus created transparent background color will be applied to widget background using setInt method on remote views object passing container element id, method name setBackgroundColor and color arguments to it.
int widgetBackground = 255 - sharedPref.getInt(
context.getString(R.string.widget_background), 255);
Color bg = Color.valueOf(context.getResources().getColor(R.color.widgetBg, null));
widgetBackground = Color.argb((float)widgetBackground, bg.red(), bg.green(), bg.blue() );
rviews.setInt(R.id.container, "setBackgroundColor", widgetBackground);
Text color is applied to text views of widget using setTextColor method of RemoteViews object passing text view id and color arguments to it.
int textcolor = sharedPref.getInt(context.getString(R.string.widget_color), R.id.black);
rviews.setTextColor(R.id.location_name, ContextCompat.getColor(context, textcolor));
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="100dp"
android:minHeight="80dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/location"
android:initialLayout="@layout/location_widget_layout"
android:resizeMode="horizontal|vertical"
android:configure="com.zoftino.locationinfowidget.WidgetConfigurationActivity"
android:widgetCategory="home_screen">
</appwidget-provider>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zoftino.locationinfowidget">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".WidgetConfigurationActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<receiver
android:name=".LocationAppWidgetProvider"
android:enabled="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.zoftino.locationinfowidget.DISPLAY_PLACE" />
<action android:name="com.zoftino.locationinfowidget.WIDGET_CLICK" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/location_widget" />
</receiver>
</application>
</manifest>