When creating a watch face for Android Wear devices, you may want to include a phone app for configuration. A preview of the watch face in the configuration app provides immediate feedback of the changes and makes it more comfortable for users to change settings.
To achieve this, you have to put the drawing code in a separate class, which can be reused in the phone app and the watch face service. First, create a separate Android Library module, where you put the common classes and resources for drawing the watch face. In this module, create an interface IWatchFaceConfig, which exposes your configuration options.
public interface IWatchFaceConfig { Calendar getCalendar(); boolean isAmbient(); boolean isLowBitAmbient(); boolean isRound(); // put your watch face options here boolean isLightTheme(); }
In your phone app and watch face service, you can now either choose to directly implement IWatchFaceConfig, or you can make a WatchFaceConfig class, if you prefer to have a separate configuration object. When you directly implement the interface, you have to be careful to avoid memory leaks (for example if your Activity implements the interface).
With the configuration interface, you can start implementing your WatchFaceDrawer, which is used by both the phone app and the watch face service.
public class WatchFaceDrawer { private boolean mIsMobilePreview = false; // put your resources here (Paint objects, dimensions, colors, ...) public WatchFaceDrawer(Context context) { Resources res = context.getResources(); // initialize your resources } public void setMobilePreview(Context context, boolean isMobilePreview) { mIsMobilePreview = isMobilePreview; Resources res = context.getResources(); // change resources here, which are different when the watch face // is drawn in the phone app or on the watch } public void onAmbientModeChanged(Context context, IWatchFaceConfig config) { Resources res = context.getResources(); // adapt watch face to ambient mode changes } public void onDraw(Context context, IWatchFaceConfig config, Canvas canvas, Rect bounds) { final Calendar calendar = config.getCalendar(); final boolean isAmbient = config.isAmbient(); final boolean isRound = config.isRound(); final boolean useLightTheme = !isAmbient && config.isLightTheme(); // draw your watch face here, using the provided canvas and bounds } }
In your watch face engine, you can now use the WatchFaceDrawer class. This also has the effect of reducing the complexity of your engine class, since you delegate the drawing functionality to another class.
public class ExampleWatchFace extends CanvasWatchFaceService { @Override public Engine onCreateEngine() { return new Engine(); } private class Engine extends CanvasWatchFaceService.Engine implements IWatchFaceConfig { WatchFaceDrawer mWatchfaceDrawer; boolean mAmbient = false; boolean mLowBitAmbient = false; boolean mIsRound = false; GregorianCalendar mCalendar = new GregorianCalendar(); boolean mLightTheme = true; @Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); mWatchfaceDrawer = new WatchFaceDrawer(getApplicationContext()); } @Override public void onApplyWindowInsets(WindowInsets insets) { super.onApplyWindowInsets(insets); mIsRound = insets.isRound(); } @Override public void onPropertiesChanged(Bundle properties) { super.onPropertiesChanged(properties); mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); } @Override public void onTimeTick() { super.onTimeTick(); invalidate(); } @Override public void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); if (mAmbient != inAmbientMode) { mAmbient = inAmbientMode; mWatchfaceDrawer.onAmbientModeChanged(getApplicationContext(), this); invalidate(); } } @Override public void onDraw(Canvas canvas, Rect bounds) { mCalendar.setTimeInMillis(System.currentTimeMillis()); mWatchfaceDrawer.onDraw(getApplicationContext(), this, canvas, bounds); } // IWatchfaceConfig @Override public Calendar getCalendar() { return mCalendar; } @Override public boolean isLightTheme() { return mLightTheme; } @Override public boolean isAmbient() { return mAmbient; } @Override public boolean isLowBitAmbient() { return mLowBitAmbient; } @Override public boolean isRound() { return mIsRound; } } }
In your phone app, you can use the WatchFaceDrawer to draw in a Bitmap via a Canvas, which can then be shown in an ImageView.
public class MainActivity extends AppCompatActivity implements IWatchFaceConfig { ImageView mWatchfaceImage; WatchFaceDrawer mWatchfaceDrawer; Calendar mCalendar = new GregorianCalendar(); Runnable mUpdateWatchfaceImageRunnable = new Runnable() { @Override public void run() { updateWatchfaceImage(); } }; int mSize = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWatchfaceImage = (ImageView) findViewById(R.id.iv_watchface); mWatchfaceDrawer = new WatchFaceDrawer(this); mWatchfaceDrawer.setMobilePreview(this, true); mSize = Math.round(320/1.5f*getResources().getDisplayMetrics().density); } @Override protected void onStart() { super.onStart(); updateWatchfaceImage(); } @Override protected void onStop() { super.onStop(); mWatchfaceImage.removeCallbacks(mUpdateWatchfaceImageRunnable); } private void updateWatchfaceImage() { mCalendar.setTimeInMillis(System.currentTimeMillis()); Bitmap bmp = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); mWatchfaceDrawer.onDraw(getApplicationContext(), this, canvas, canvas.getClipBounds()); mWatchfaceImage.setImageBitmap(bmp); mWatchfaceImage.postDelayed(mUpdateWatchfaceImageRunnable, 1000); } // IWatchFaceConfig @Override public Calendar getCalendar() { return mCalendar; } @Override public boolean isAmbient() { return false; } @Override public boolean isLowBitAmbient() { return false; } @Override public boolean isRound() { return true; } @Override public boolean isLightTheme() { return true; } }
Now you have a watch face with a preview in the phone app, both using the same class for drawing. When preferences are changed, you just have to update the IWatchFaceConfig fields and call onDraw() again to update the watch face.
You can clone an example app with a simple watch face from GitHub. My watch face Airli was also created using this idea, if you want to see a more complex example in action.