HTC Scribe
HTC Scribe Pen API Sample Code
The following code snippets based on the pen sample code demos illustrate the ability to use the pen and control its properties as well as setting up a painting drawing view in various scenarios. The complete source code is available as part of the SDK download.
Pen Demos
There are seven sample code projects included with the HTC OpenSense Tablet SDK Add-on: (see Advanced Sample Code section for additional sample projects)
HelloPen - minimal code required to create a pen app using the full HTC Pen framework
PenMenu - illustrates the use of the system wide Pen menu control.
HtcScrollPainting - illustrates use of stroke groups, zooming and panning beyond the visible screen.
PenEventDemo - illustrates how to use the low level pen events and buttons when providing your own source code for drawing (sample included) without using the full Pen framework.
SingleAPKDemo - demonstrates how to support both pen enabled devices and no-pen enabled devices within the same apk.
PenEventCompatibilityDemo - an alternate version of the PenEvent sample code that illustrates compatibility with the emulator and non-pen enabled devices using the same drawing code. This code includes a pencompatibility distributable jar file and doesn't require targeting the HTC add-on.
PenTouchPaintDemo - This is another compatibility example that also uses the pencompatibility jar file and also can simply target Android API Level 15. This is a port of Google's APIDemo that illustrates how to use the new ICS (Andorid 4.0) low level Pen API in a compatible way that also runs on Honeycomb (Android 3.0) devices.
Hello Pen Demo
The Hello Pen is the cannonical Hello World using the full HTC Scribe Pen Framework. This demonstrates the minimal coding required to get you up and running with a HtcPaintingView. It demonstrates where in the main layout xml you can change backgrounds and how you can easily code your first game, e.g. tic tac toe.
The following is essentially the entire code in the onCreate method, there are basically only 3 steps you have to add.- Add HtcPaintingView to your layout and activity
- Initialize HtcPaintingView with a SerializeDAO
- Set group IDs and ViewPorts on painting view
Eclipse screenshot with collapsed code illustrating the minimal code required:
PenMenu Demo
In this demo we are using the PenMenu control, providing a drawing area to the right of it and 2 spinner controls (list of choices) for both the pen type and which actions to provide within the pen menu control. A button provides the ability to show or hide the PenMenu control.
The main layout, main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#665588"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/spinner_pen_set"
/>
<Spinner android:id="@+id/select_pen_set"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</Spinner>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/spinner_action_button"
/>
<Spinner android:id="@+id/disable_action_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</Spinner>
<Button android:id="@+id/queryAlpha"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/query_alpha">
</Button>
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.htc.painting.engine.HtcPaintingView
android:id="@+id/paintingView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<FrameLayout
android:layout_gravity="bottom|right"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<com.htc.painting.penmenu.PenMenu
android:id="@+id/pen_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
</FrameLayout>
</LinearLayout>
In MainActivity.java:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
public void init() {
penMenu = (PenMenu)findViewById(R.id.pen_menu);
paintingView = (HtcPaintingView)findViewById(R.id.paintingView);
// Initialize the ink overlay (Htc Painting View)
HtcPaintingViewPort mHtcPaintingViewPort = new HtcPaintingViewPort();
paintingView.setEnabled(true);
FileSerializeDAO dao = new FileSerializeDAO(null);
HtcPaintingViewPort viewPort[] = {mHtcPaintingViewPort};
int group[] = {0};
paintingView.init(dao);
paintingView.requestStrokeGroups(group, viewPort);
// set painting view to pen menu
penMenu.setPaintingView(paintingView);
// testing UI to select pen set
selectPenSet = (Spinner) findViewById(R.id.select_pen_set);
ArrayAdapter<CharSequence> adapter
=ArrayAdapter.createFromResource(this, R.array.pen_set, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
selectPenSet.setAdapter(adapter);
selectPenSet.setOnItemSelectedListener(
new OnItemSelectedListener() {
public void onItemSelected(
AdapterView<?> parent, View view, int position, long id) {
if (id >= 0 && id <= 2) {
mCurrentPenSet = position;
} else {
mCurrentPenSet = 0;
}
// React the UI customized change when showing PenMenu next time
penMenu.hide();
// React the UI customized change when showing PenMenu next time
penMenu.setPenSetType(mCurrentPenSet);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
....
The PenMenu and PaintingView are retrieved from the layout, the PaintingView attached to the PenMenu and initialized with a file serializable data access object which is defined in a separate class and in this example a placeholder used for saving your drawings to a file.
A dropdown spinner resource is used to select a pen from a pen set, and its adapter and listener setup is shown above. The additional spinner and button left out from the listing above is also setup in onCreate().
Saving instance state using a bundle:
protected void onRestoreInstanceState(Bundle savedInstanceState) {
mCurrentPenSet = savedInstanceState.getInt("penset", 0);
selectPenSet.setSelection(mCurrentPenSet);
mActionButtonID = savedInstanceState.getInt("action", 0);
selectActionButton.setSelection(mActionButtonID);
penMenu.OnRestoreInstanceState(savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("action", mActionButtonID);
outState.putInt("penset", mCurrentPenSet);
penMenu.OnSaveInstanceState(outState);
}
Handling the touch events:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isPen = PenEvent.isPenEvent(event);
if (!isPen) {
return super.dispatchTouchEvent(event);
}
int action = PenEvent.PenAction(event);
int buttonStates = PenEvent.PenButton(event);
switch (buttonStates) {
case PenEvent.PEN_BUTTON_NONE:
penMenu.setEraserMode(false);
penMenu.forceHideStroke(false);
break;
case PenEvent.PEN_BUTTON2:
if (action == PenEvent.PEN_ACTION_DOWN) {
penMenu.setEraserMode(true);
}
else if (action == PenEvent.PEN_ACTION_UP) {
penMenu.setEraserMode(false);
}
return super.dispatchTouchEvent(event);
case PenEvent.PEN_BUTTON1:
if (action == PenEvent.PEN_ACTION_DOWN)
penMenu.forceHideStroke(true);
else if (action == PenEvent.PEN_ACTION_UP)
penMenu.forceHideStroke(false);
return true;
}
return super.dispatchTouchEvent(event);
}
PenEvent.isPenEvent(event) is used to determine if the MotionEvent is a pen event and then handled using the predefined pen event actions. The action is used to set the corresponding mode on the pen menu. Likewise we can handle shortcut keys by passing the KeyEvent to the pen menu's handler.
public boolean dispatchKeyEvent( KeyEvent event){
int keyCode = event.getKeyCode();
if(penMenu.handleKeyEvent(keyCode, event) == true) {
return true;
}
return super.dispatchKeyEvent(event);
}
This example lays the ground work for effectively using the PenMenu controller, in this next example we could've also used the PenMenu again for selecting pen attributes but we will demonstrate how to use framework to implement a custom PenMenu equivalent as shown.
Pen Scroll Painting Demo
In this demo we'll add an eraser, but more importantly the ability to draw on a view that can scroll off the view port off of the screen. Additionally we'll show how to set properties without the pen menu by creating your own custom dialogs and choice lists using a standard android system menu that can also be adapted for use as the applications' Android 3.1 Options style settings.
In HtcScrollPaintingActivity.java:
Note that HtcScrollPaintingActivity implements ColorPickerDialog.OnColorChangedListener and OnCacheListener.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
mFileSerializeDAO = new FileSerializeDAO(this);
mScrollView = (MyScrollView) this.findViewById(R.id.ScrollView01);
mHtcPaintingView = (HtcPaintingView) findViewById(R.id.gestures);
mHtcPaintingView.init(mFileSerializeDAO);
mProperties = new StrokeProperties();
setScrollListener();
mHtcPaintingView.setOnCacheListener(this);
initiDrawerGroup();
getPenList();
mHtcPaintingView.setDrawingPartialInvalidate(true);
mHtcPaintingView.setInkDrawingCacheRatio(1.0f, 2.0f);
mHtcPaintingView.setDocViewPort(mStrokeGroupViwePortTable.get(0));
mHtcPaintingView.setSeparatorDist(500);
}
Note that once again we setup our view, this time with some additional calls such as to setup invalidation (because of scrolling), the viewport, and additional items like a cache listener.
In MyScrollView.java:
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mListener != null)
mListener.onScrollChange(l, t, oldl, oldt);
}
public static interface OnScrollListener {
void onScrollChange(int l, int t, int oldl, int oldt);
}
Note that MyScrollView extends ScrollView so we're only adding listeners that we need, the functionality is already there.
One more thing we haven't covered is out instance of a ViewPort which provide the actual Rectangle we are currently viewing and drawing on, i.e. the visible region.
public class HtcScrollPaintingViewPort extends ViewPort {
private WeakReference<ScrollView> mAppScrollView = null;
//visible region with coordinate based on the document's origin
private RectF mVisibleRegion = null;
private int mGroupId = -1;
public HtcScrollPaintingViewPort(ScrollView sv, RectF visiableRegion,
int groupId) {
mAppScrollView = new WeakReference<ScrollView>(sv);
mVisibleRegion = visiableRegion;
mGroupId = groupId;
}
In the code you may have also noticed that this example reuses the volume keys so that we can also zoom in and out on our painting view. Hopefully all the parts needed for a fully functional drawing application - see additional comments in the source for additional notes on what's missing.
PenEvent Demo
As described in the overview this is a simpler example in that it doesn't using the full painting framework packages and just uses the low level API found in the PenEvent class. This isn't the preferred API to use since you would have to re-implement the drawing implementation of the pen API framework for a fully featured drawing application, however it's provided for those only interested in the low level pen events (e.g. for porting existing code)..
In this example we are creating a simple drawing application with all the drawing plumbing within the example using just simple canvas drawing methods. The pen's two buttons are used,The first one to call up a color pallete, the second to erase. Note that a best practice is to provide am alternate way to access the functionality that the buttons provide. This example also demonstrates how to handle both touch and pen events in the same onTouchEvent method. In this case, touch events also erase, which means you can use the back of the pen to erase as well! Also illustrated is pen pressure to set line wisth, using MotionEvent's getPressure() method.
From PenEventDemoActivity.java:
public boolean onTouchEvent(MotionEvent event) {
// handle pen event:
if (PenEvent.isPenEvent(event)) {
int action = PenEvent.PenAction(event);
int button = PenEvent.PenButton(event);
currentWidth = 10 * event.getPressure();
if (checkPenButtons(button, event)) {
return true;
}
switch (action) {
case PenEvent.PEN_ACTION_DOWN:
handleAction(ACTION_DOWN, event.getX(), event.getY());
break;
case PenEvent.PEN_ACTION_UP:
handleAction(ACTION_UP, event.getX(), event.getY());
break;
case PenEvent.PEN_ACTION_MOVE:
handleAction(ACTION_MOVE, event.getX(), event.getY());
break;
}
} else {
// handle touch event
handleErase(event);
}
return true;
}
public void handleErase(MotionEvent event) {
for(int i = 0; i < pointsX.size(); i++) {
Float x = pointsX.get(i);
Float y = pointsY.get(i);
if ( null != x && null != y
&& Math.abs(x-event.getX()) < ERASE_RADIUS
&& Math.abs(y-event.getY()) < ERASE_RADIUS) {
pointsX.set(i, null);
pointsY.set(i, null);
}
}
}
public void handleAction(int action, float x, float y) {
synchronized (renderer.getSurfaceHolder()) {
if (action == ACTION_DOWN) {
pointsX.add(null);
pointsY.add(null);
colors.add(currentColor);
widths.add(currentWidth);
}
pointsX.add(x);
pointsY.add(y);
colors.add(currentColor);
widths.add(currentWidth);
}
}
private boolean checkPenButtons(int penBtn, MotionEvent event) {
switch (penBtn) {
case PenEvent.PEN_BUTTON_NONE:
// Do nothing.
break;
case PenEvent.PEN_BUTTON1:
if (!isShowingColorPicker) {
Dialog colorPicker = new ColorPickerDialog(
PenEventCompatibilityDemoActivity.this,
PenEventCompatibilityDemoActivity.this, paint.getColor());
colorPicker.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
isShowingColorPicker = false;
}
});
isShowingColorPicker = true;
colorPicker.show();
}
case PenEvent.PEN_BUTTON2:
handleErase(event);
return true;
}
return false;
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
Float lastX = null;
Float lastY = null;
for (int i = 0; i < pointsX.size(); i++) {
Float x = pointsX.get(i);
Float y = pointsY.get(i);
if (null != lastX && null != lastY && null != x && null != y) {
paint.setColor(colors.get(i));
paint.setStrokeWidth(widths.get(i));
canvas.drawLine(lastX, lastY, x, y, paint);
}
lastX = x;
lastY = y;
}
}
So basically after we determine the MotionEvent is a PenEvent, we retrieve the PenAction and PenButton, and we handle those in corresponding methods as well as store the pressure level.
PenEvent Compatibility Demo
PenEventCompatibilityDemo and PenEventDemo are identical except for the following code changes which require the accompanying compatibility jar file:
Included is the PenEventCompatibilty jar library to be packaged with your application should you want to write a low level PenEvent application that supports both Android 3.1 and Android 2.3 as well as support for non-pen enabled devices such as the emulator.
Note: This library jar file must be added to build the project in Eclipse. From Project Properties -> Java Build Path -> Libraries -> Add Jars..., select the PenEventCompatibilty.jar file from the project's libs folder. If you imported the sample code into Eclipse, this is already taken care of.
The following are the only changes made to the PenEventDemo sample:
- 1) import com.htc.pen.compat.PenEvent instead of com.htc.pen.PenEvent
- 2) extend PenEventActivity instead of Activty
- 3) add registerView(View view) in onCreate()
- note: this 3rd item is only for 3.x support if the drawing view not set in setContentView()
import com.htc.pen.compat.PenEvent;
import com.htc.pen.compat.PenEventActivity;
public class PenEventDemoCompatActivity extends PenEventActivity {
private SketchPad pad;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pad = new SketchPad(this);
setContentView(pad);
// only required for GB compatibility if view not set in setContentView()
//registerView(pad);
}
Optionally, again just as part of this compatibility example, there is the option to support the pen menu key with the additional methods: onPenMenuDown and onPenMenuUp. PenMenu support by default allows the system wide pen menu default support to be displayed just as with the PenEventDemo example.
The drawing code doesn't change, and this should work (again limited to just the low level API) on both Android 3.1 (Honeycomb) and Android 2.3.x (Gingerbread) devices that support the scribe pen. Emulator support is limited to just this compatibility example, use 'P' or 'Q' keys to indicate the pen touching the screen and '1' and '2' keys for the 2 buttons on the pen and 'M" for the optional onMenuKeyDown/Up support.
Single APK Demo
This example demonstrates how to provide pen API functionality for pen enabled devices as well as support for non-pen enabled devices using the same codebase and providing only one (a single) APK application file. In this example we add pen functionality as a separate activity and provide the option to use it to the user at runtime by enabling the appropriate button. For illustrating both options, two buttons are shown in this demo for both pen enabled and non-pen enabled devices. In practice, one of the buttons would probably be set to be invisible rather than disabled. Since the activities load the classes at runtime, one can include all the code to support both cases.
The following code illustrates one way to detect whether the device supports the HTC Scribe Pen at runtime:
public class PenFeatureDetector {
/**
* Accesses methods that exist only on Android API level 5 or later.
*/
private static class SDK5Operations {
/**
* @param context Context used to access PackageManager
* @return true if running on an Android device with the pen hardware feature, false otherwise
*/
private static boolean hasPenFeature(final Context context) {
final PackageManager pm = context.getPackageManager();
for (final FeatureInfo feature : pm.getSystemAvailableFeatures()) {
if (PEN_FEATURE_NAME.equals(feature.name)) {
return true;
}
}
return false;
}
}
/**
* The name of a class used in the HTC Scribe pen API.
*/
private static final String PEN_EVENT_CLASS_NAME = "com.htc.pen.PenEvent";
/**
* The name of the Android feature that indicates a pen.
*/
private static final String PEN_FEATURE_NAME = "android.hardware.touchscreen.pen";
/**
* @param context Context used to access PackageManager
* @return true if running on a device with the HTC Scribe pen supported, false otherwise
*/
public static boolean hasPenEvent(final Context context) {
return hasHtcPenEventClass() && hasPenFeature(context);
}
/**
* @return true if HTC PenEvent class to access pen data is available, false otherwise
*/
private static boolean hasHtcPenEventClass() {
try {
Class.forName(PEN_EVENT_CLASS_NAME);
} catch (final ClassNotFoundException e) {
return false;
}
return true;
}
/**
* @param context Context used to access PackageManager
* @return true if running on an Android device with the pen hardware feature, false otherwise
*/
private static boolean hasPenFeature(final Context context) {
// Return false if the version of Android doesn't have the needed method.
if (Integer.parseInt(Build.VERSION.SDK) < 5) {
return false;
}
// Otherwise check for the feature.
return SDK5Operations.hasPenFeature(context);
}
}
PenTouchPaintDemo
This project illustrates another compatibility example using the pen compatibility jar library. In this case we are porting the ICS (Android 4.0) demo found in Google's APIDemos which uses the new standard ICS low level Pen API.
With minor changes (MotionEventCompat in place of MotionEvent) the code can be made to work in HoneyComb (Android 3.x) devices and as before can call back to run in the emulator (non-pen enabled devices).
As has been the case with the PenEventCompatibility demo example, you can easily target Android API Level 15 and not have to target the HTC OpenSense SDK add-on.