Quantcast
Channel: Maui Mauer
Viewing all articles
Browse latest Browse all 2

Entwickeln mit dem S-Pen und Samsung Galaxy Note 8.0

$
0
0

Mittlerweile gibt es Android für viele verschiedene Gerätetypen- und Formate, welche sich mitunter deutlich unterscheiden. Hersteller wie Samsung versuchen zudem Ihre Geräte durch Beigaben oder “besondere Features” von der restlichen Konkurrenz abzusetzen. Ein Beispiel hierfür ist die in der Galaxy Note-Serie zum Einsatz kommende S-Pen Technologie, welche zusammen mit Wacom, den Spezialisten für digitale Zeichentablets, entwickelt wurde. Sie erlaubt es mittels Stylus auf dem Display mit hoher Genauigkeit zu malen, zeichnen oder schreiben. Die aktuelle Generation ist zudem in der Lage bereits Eingaben zu erkennen, wenn der Stylus etwas über dem Display gehalten wird.

Auch für unabhängige Android-Entwickler kann es von Vorteil sein, diese optionalen Features in den eigenen Apps zu unterstützen. Dies ist von Samsung auch so gewollt, im Entwicklerbereich des Unternehmens wird das S-Pen SDK zum freien Download angeboten.

In diesem Beitrag zeige ich wie mit vergleichsweise wenig Aufwand der Samsung S-Pen in einer eigenen Applikation unterstützt werden kann. Dieser Beitrag richtet sich an Entwickler, welche bereits Erfahrungen mit Android gesammelt haben und dürfte nicht unbedingt für blutige Anfänger geeignet sein. Für alle, die sich lieber nicht gleich in den Quelltext stürzen, gibt es hier eine fertig kompilierte .apk zum ausprobieren.

Schritt 0 – Entwicklungsumgebung

Android Studio 0.1.1

Auf der diesjährigen (2013) Google I/O wurde die neue auf IntelliJ basierende Entwicklungsumgebung Android Studio vorgestellt. Unser Projekt werden wir in dieser IDE mit dem neuen Android Buildsystem Gradle erstellen. Android Studio wird für alle gängigen Plattformen auf developer.android.com zum Download angeboten.

Schritt 1 – S-Pen SDK

Die aktuelle Version 2.3 des S-Pen SDK kann hier heruntergeladen werden. Das SDK besteht aus einer nativen Komponente (kompiliert für ARM) und einer Java-Bibliothek.

project_treeSchritt 2 – Android Studio und Gradle

Zunächst erstellen wir ein neues Projekt in Android Studio. Da wir dieses Projekt für das Galaxy Note 8.0 entwickeln, können wir das geforderte API-Level recht hoch ansetzen, da das Note 8.0 bereits mit Android 4.1 ausgeliefert wird (auch alle anderen “aktuellen” Geräte aus der Galaxy Note-Serie (Note 10.1,  Note 2) entsprechen diesen Anforderungen.

Android Studio bietet verschiedene Starter-Templates bei der Erstellung einer neuen App an. Für unsere Zwecke ist das “Master/Detail Flow”-Template ideal, es ist bereits für die Anzeige auf Tablets optimiert und erlaubt es uns eine Liste sowie eine zugehörige Detailansicht zu verwenden. In unserem Fall werden wir im linken Fragment eine Liste unserer Zeichnungen anzeigen und den rechten Detailbereich für das Malen mit dem S-Pen reservieren.

Android Studio setzt auf das neue Android Buildsystem Gradle. Gradle baut auf einigen Konzepten von Ant und Maven auf und bedient sich einer auf Groovy basierenden Sprache um die Buildkonfiguration anzugeben. Gradle ist in der Lage Build-Abhängigkeiten über Maven Repositories aufzulösen, sodass die meisten Dependencies nicht mehr händisch kopiert und eingefügt werden müssen. Hier ist das bereits für unser Projekt angepasste Buildfile (build.gradle):

buildscript {
    repositories {
        maven { url 'http://repo1.maven.org/maven2' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.4'
    }
}
apply plugin: 'android'

repositories {
    mavenCentral()
}

dependencies {
    compile files('libs/android-support-v4.jar')
    compile files('libs/libspen23.jar')
    compile 'org.roboguice:roboguice:2.0'
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        minSdkVersion 11
        targetSdkVersion 16
    }
}

task copyNativeLibs(type: Copy) {
    from(new File(projectDir, 'native-libs')) { include '**/*.so' }
    into new File(buildDir, 'native-libs')
}

tasks.withType(Compile) { compileTask -> compileTask.dependsOn copyNativeLibs }

clean.dependsOn 'cleanCopyNativeLibs'

tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask ->
    pkgTask.jniDir new File(buildDir, 'native-libs')
}

Das bereits von Android Studio generierte Buildfile sieht unserer Version bereits sehr ähnlich, ein paar Anpassungen sind aber dennoch nötig. Das S-Pen SDK ist zur Zeit leider nicht über Maven verfügbar und muss deshalb manuell von uns in libs/ platziert werden. Die Nativen Komponenten des S-Pen SDK platzieren wir in einem (neuen) Ordner namens native-libs, (./native-libs/armeabi/*.so)

dependencies {
    compile files('libs/android-support-v4.jar')
    compile files('libs/libspen23.jar')
    compile 'org.roboguice:roboguice:2.0'
}

Hier spezifizieren wir das S-Pen SDK (libs/libspen23.jar) in unserem Projekt verweden möchten. Die Zeile darunter ist eine Build-Abhängikeit gegen Roboguice, welche von Gradle über das Maven Central Repository automatisch aufgelöst wird.

task copyNativeLibs(type: Copy) {
    from(new File(projectDir, 'native-libs')) { include '**/*.so' }
    into new File(buildDir, 'native-libs')
}

tasks.withType(Compile) { compileTask -> compileTask.dependsOn copyNativeLibs }

clean.dependsOn 'cleanCopyNativeLibs'

tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask ->
    pkgTask.jniDir new File(buildDir, 'native-libs')
}

Diese Anpassung ist nötig, da unser S-Pen SDK native Komponenten enthält, welche ebenfalls in unserem fertigen .apk enthalten sein müssen. Wir erweitern also Gradle’s “compile” Task und geben das an alle Shared Objects aus unserem Ordner native-libs/ in unser Buildverzeichnis kopiert werden sollen.

Android Studio hat den größten Teil des Codes, den wir für unsere kleine Mal-App benötigen, bereits generiert und, um das S-Pen SDK zu integrieren werden wir primär unser DrawingDetailFragment anpassen.

package de.mobilers.android.spensample.fragments;

import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.samsung.samm.common.SOptionSCanvas;
import com.samsung.spensdk.SCanvasConstants;
import com.samsung.spensdk.SCanvasView;
import com.samsung.spensdk.applistener.SCanvasInitializeListener;
import com.samsung.spensdk.applistener.SCanvasModeChangedListener;
import com.samsung.spensdk.example.tools.SPenSDKUtils;

import java.io.File;
import java.util.HashMap;

import de.mobilers.android.spensample.Consts;
import de.mobilers.android.spensample.R;
import de.mobilers.android.spensample.dummy.DummyContent;
import roboguice.fragment.RoboFragment;
import roboguice.inject.InjectView;

/**
 * A fragment representing a single Drawing detail screen.
 * This fragment is either contained in a {@link de.mobilers.android.spensample.activity.DrawingListActivity}
 * in two-pane mode (on tablets) or a {@link de.mobilers.android.spensample.activity.DrawingDetailActivity}
 * on handsets.
 */
public class DrawingDetailFragment extends RoboFragment {

    @InjectView(R.id.canvas_container)
    private RelativeLayout mCanvasContainer;

    @InjectView(R.id.layout_container)
    private FrameLayout mLayoutContainer;

    private SCanvasView mSCanvas;
    private File mDrawingFile;

    public static final String TAG = "DrawingDetailFragment";

    /**
     * The fragment argument representing the item ID that this fragment
     * represents.
     */
    public static final String ARG_ITEM_ID = "item_id";

    /**
     * The dummy content this fragment is presenting.
     */
    private DummyContent.DummyItem mItem;

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public DrawingDetailFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getArguments().containsKey(ARG_ITEM_ID)) {
            // Load the dummy content specified by the fragment
            // arguments. In a real-world scenario, use a Loader
            // to load content from a content provider.
            mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
        }
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.drawing_menu, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        if(mSCanvas != null) {
            menu.findItem(R.id.menu_undo).setEnabled(mSCanvas.isUndoable());
            menu.findItem(R.id.menu_redo).setEnabled(mSCanvas.isRedoable());

            if(!mSCanvas.isFingerControlPenDrawing())
                menu.findItem(R.id.menu_pen_only).setIcon(R.drawable.selector_penonly_n);
            else
                menu.findItem(R.id.menu_pen_only).setIcon(R.drawable.selector_penonly);
        }

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        getActivity().invalidateOptionsMenu();
        int id = item.getItemId();
        switch(id) {
            case R.id.menu_undo:
                mSCanvas.undo();
                break;
            case R.id.menu_redo:
                mSCanvas.redo();
                break;
            case R.id.menu_colorPicker:
                boolean bIsColorPickerMode = !mSCanvas.isColorPickerMode();
                mSCanvas.setColorPickerMode(bIsColorPickerMode);
                break;
            case R.id.menu_pen_only:
                boolean bIsPenOnly = !mSCanvas.isFingerControlPenDrawing();
                mSCanvas.setFingerControlPenDrawing(bIsPenOnly);
                break;
            case R.id.menu_pen:
                if(mSCanvas.getCanvasMode()==SCanvasConstants.SCANVAS_MODE_INPUT_PEN){
                    mSCanvas.setSettingViewSizeOption(SCanvasConstants.SCANVAS_SETTINGVIEW_PEN, SCanvasConstants.SCANVAS_SETTINGVIEW_SIZE_EXT);
                    mSCanvas.toggleShowSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_PEN);
                }
                else{
                    mSCanvas.setCanvasMode(SCanvasConstants.SCANVAS_MODE_INPUT_PEN);
                    mSCanvas.showSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_PEN, false);
                    updateModeState();
                }
                break;
            case R.id.menu_erase:
                if(mSCanvas.getCanvasMode()==SCanvasConstants.SCANVAS_MODE_INPUT_ERASER){
                    mSCanvas.setSettingViewSizeOption(SCanvasConstants.SCANVAS_SETTINGVIEW_ERASER, SCanvasConstants.SCANVAS_SETTINGVIEW_SIZE_NORMAL);
                    mSCanvas.toggleShowSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_ERASER);
                }
                else {
                    mSCanvas.setCanvasMode(SCanvasConstants.SCANVAS_MODE_INPUT_ERASER);
                    mSCanvas.showSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_ERASER, false);
                    updateModeState();
                }
                break;
            case R.id.menu_text:
                if(mSCanvas.getCanvasMode()==SCanvasConstants.SCANVAS_MODE_INPUT_TEXT){
                    mSCanvas.setSettingViewSizeOption(SCanvasConstants.SCANVAS_SETTINGVIEW_TEXT, SCanvasConstants.SCANVAS_SETTINGVIEW_SIZE_NORMAL);
                    mSCanvas.toggleShowSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_TEXT);
                }
                else{
                    mSCanvas.setCanvasMode(SCanvasConstants.SCANVAS_MODE_INPUT_TEXT);
                    mSCanvas.showSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_TEXT, false);
                    updateModeState();
                    Toast.makeText(getActivity(), "Tap Canvas to insert Text", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.menu_filling:
                if(mSCanvas.getCanvasMode()==SCanvasConstants.SCANVAS_MODE_INPUT_FILLING){
                    mSCanvas.setSettingViewSizeOption(SCanvasConstants.SCANVAS_SETTINGVIEW_FILLING, SCanvasConstants.SCANVAS_SETTINGVIEW_SIZE_NORMAL);
                    mSCanvas.toggleShowSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_FILLING);
                }
                else{
                    mSCanvas.setCanvasMode(SCanvasConstants.SCANVAS_MODE_INPUT_FILLING);
                    mSCanvas.showSettingView(SCanvasConstants.SCANVAS_SETTINGVIEW_FILLING, false);
                    updateModeState();
                    Toast.makeText(getActivity(), "Tap Canvas to fill color", Toast.LENGTH_SHORT).show();
                }
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_drawing_detail, container, false);

        // Show the dummy content as text in a TextView.
        if (mItem != null) {

        }

        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated()");
        super.onActivityCreated(savedInstanceState);

        mSCanvas = new SCanvasView(getActivity());
        mSCanvas.addedByResizingContainer(mCanvasContainer);

        HashMap<String,Integer> settingResourceMapInt = SPenSDKUtils.getSettingLayoutLocaleResourceMap(true, true, true, true);
        SPenSDKUtils.addTalkbackAndDescriptionStringResourceMap(settingResourceMapInt);
        HashMap<String,String> settingResourceMapString = SPenSDKUtils.getSettingLayoutStringResourceMap(true, true, true, true);
        mSCanvas.createSettingView(mLayoutContainer, settingResourceMapInt, settingResourceMapString);

        mSCanvas.setSCanvasInitializeListener(new SCanvasInitializeListener() {
            @Override
            public void onInitialized() {
                //--------------------------------------------
                // Start SCanvasView/CanvasView Task Here
                //--------------------------------------------
                // Application Identifier Setting
                if(!mSCanvas.setAppID(Consts.APPLICATION_ID_NAME, Consts.APPLICATION_ID_VERSION_MAJOR, Consts.APPLICATION_ID_VERSION_MINOR, Consts.APPLICATION_ID_VERSION_PATCHNAME))
                    Toast.makeText(getActivity(), "Fail to set App ID.", Toast.LENGTH_LONG).show();

                // Set Title
                if(!mSCanvas.setTitle("mobilers S-Pen Sample"))
                    Toast.makeText(getActivity(), "Fail to set Title.", Toast.LENGTH_LONG).show();

                // Set Initial Setting View Size
                mSCanvas.setSettingViewSizeOption(SCanvasConstants.SCANVAS_SETTINGVIEW_PEN, SCanvasConstants.SCANVAS_SETTINGVIEW_SIZE_EXT);

                // Set Editor Version (mEditorGUIStyle)
                mSCanvas.setSCanvasGUIStyle(SCanvasConstants.SCANVAS_GUI_STYLE_NORMAL);

                // Set Pen Only Mode with Finger Control
                mSCanvas.setFingerControlPenDrawing(true);

                // Set Editor GUI Style (mbSingleSelectionFixedLayerMode)
                // - true :  S Pen SDK 2.2 (Single selection, Fixed layer Editor : Image-Text-Stroke ordering)
                // - false : S Pen SDK 2.3 (Multi-selection, Flexible layer Editor : Input ordering)
                mSCanvas.setSingleSelectionFixedLayerMode(false);

                // Update button state
                updateModeState();

                mDrawingFile = new File(getActivity().getDir("drawings", Context.MODE_PRIVATE).getAbsolutePath()+"/"+mItem.id+".png");

                // Load the file & set Background Image
                if(mDrawingFile.exists()){

                    if(SCanvasView.isSAMMFile(mDrawingFile.getAbsolutePath())){
                        loadSAMMFile(mDrawingFile.getAbsolutePath());
                        // Set the editing rect after loading
                    }
                }

            }
        });

        mSCanvas.setSCanvasModeChangedListener(new SCanvasModeChangedListener() {

            @Override
            public void onModeChanged(int mode) {
                updateModeState();
            }

            @Override
            public void onMovingModeEnabled(boolean bEnableMovingMode) {
                updateModeState();
            }

            @Override
            public void onColorPickerModeEnabled(boolean bEnableColorPickerMode) {
                updateModeState();
            }
        });

        mSCanvas.setSCanvasHoverPointerStyle(SCanvasConstants.SCANVAS_HOVERPOINTER_STYLE_SPENSDK);
    }

    @Override
    public void onStart() {
        super.onStart();

        getActivity().invalidateOptionsMenu();
    }

    // Load SAMM file
    boolean loadSAMMFile(String strFileName){
        Log.d(TAG, "loadSAMMFile()");
        if(mSCanvas.isAnimationMode()){
            // It must be not animation mode.
        }
        else {
            // set progress dialog
            mSCanvas.setProgressDialogSetting(R.string.load_title, R.string.load_msg, ProgressDialog.STYLE_HORIZONTAL, false);

            // canvas option setting
            SOptionSCanvas canvasOption = mSCanvas.getOption();
            if(canvasOption == null)
                return false;
           /* canvasOption.mSAMMOption.setConvertCanvasSizeOption(PreferencesOfSAMMOption.getPreferenceLoadCanvasSize(getActivity()));
            canvasOption.mSAMMOption.setConvertCanvasHorizontalAlignOption(PreferencesOfSAMMOption.getPreferenceLoadCanvasHAlign(getActivity()));
            canvasOption.mSAMMOption.setConvertCanvasVerticalAlignOption(PreferencesOfSAMMOption.getPreferenceLoadCanvasVAlign(getActivity()));
            canvasOption.mSAMMOption.setDecodePriorityFGData(PreferencesOfSAMMOption.getPreferenceDecodePriorityFGData(getActivity()));*/
            // option setting
            mSCanvas.setOption(canvasOption);

            // show progress for loading data
            if(mSCanvas.loadSAMMFile(strFileName, true, true, true)){
                // Loading Result can be get by callback function
            }
            else{
                Toast.makeText(getActivity(), "Load AMS File("+ strFileName +") Fail!", Toast.LENGTH_LONG).show();
                return false;
            }
        }
        return true;
    }

    private void updateModeState(){
        //SPenSDKUtils.updateModeState(mSCanvas, null, null, mPenBtn, mEraserBtn, mTextBtn, mFillingBtn, mInsertBtn, mColorPickerBtn, null);
    }

    private void callGalleryForInputImage(int nRequestCode){
        try {
            Intent galleryIntent;
            galleryIntent = new Intent();
            galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
            galleryIntent.setType("image/*");
            galleryIntent.setClassName("com.cooliris.media", "com.cooliris.media.Gallery");
            startActivityForResult(galleryIntent, nRequestCode);
        } catch(ActivityNotFoundException e) {
            Intent galleryIntent;
            galleryIntent = new Intent();
            galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
            galleryIntent.setType("image/*");
            startActivityForResult(galleryIntent, nRequestCode);
            e.printStackTrace();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause()");
        if(!mSCanvas.saveSAMMFile(mDrawingFile.getAbsolutePath())) {
            Toast.makeText(getActivity(),"Saving failed...", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        mSCanvas.closeSCanvasView();
    }
}

300 Zeilen Code, viel davon noch nicht einmal spezifisch für das S-Pen SDK und schon haben wir ein Fragment, in dem mit dem S-Pen nach Herzenslust gemalt und gezeichnet werden kann. Es hat sicherlich nicht den Funktionsumfang der mitgelieferten S-Note App, aber es ist genug, um über weitere Einsatzmöglichkeiten nachzudenken. Handschriftliche Kommentare oder Beiträge für Blogs oder vielleicht ein Feld das Unterschriften aufnehmen kann.

mSCanvas = new SCanvasView(getActivity());
mSCanvas.addedByResizingContainer(mCanvasContainer);

Mit diesen zwei Zeilen initialisieren wir bereits ein nutzbares SCanvas, der restlichen Code dient lediglich zum Umschalten zwischen den verschiedenen Modi und, um den im S-Pen SDK enthaltenen Einstellungsdialog aufzurufen. Unser Code läuft zudem nicht ausschließlich auf dem Galaxy Note 8.0, sondern funktioniert auch auf den anderen aktuellen Geräten der Galaxy Note Serie.

Das fertige Projekt ist auf GitHub verfügbar.

The post Entwickeln mit dem S-Pen und Samsung Galaxy Note 8.0 appeared first on Maui Mauer.


Viewing all articles
Browse latest Browse all 2

Latest Images

Pangarap Quotes

Pangarap Quotes

Vimeo 10.7.0 by Vimeo.com, Inc.

Vimeo 10.7.0 by Vimeo.com, Inc.

HANGAD

HANGAD

MAKAKAALAM

MAKAKAALAM

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Trending Articles


Break up Quotes Tagalog Love Quote – Broken Hearted Quotes Tagalog


Gwapo Quotes : Babaero Quotes


Winx Club para colorear


Girasoles para colorear


Dibujos para colorear de perros


Toro para colorear


Lagarto para colorear


Long Distance Relationship Tagalog Love Quotes


Tropa Quotes


Mga Tala sa “Unang Siglo ng Nobela sa Filipinas” (2009) ni Virgilio S. Almario


Ang Nobela sa “From Darna to ZsaZsa Zaturnnah: Desire and Fantasy, Essays on...


Kung Fu Panda para colorear


Libros para colorear


Mandalas de flores para colorear


Renos para colorear


Dromedario para colorear


mayabang Quotes, Torpe Quotes, tanga Quotes


Love Quotes Tagalog


RE: Mutton Pies (mely)


El Vibora (1971) by Francisco V. Coching and Federico C. Javinal





Latest Images

Pangarap Quotes

Pangarap Quotes

Vimeo 10.7.0 by Vimeo.com, Inc.

Vimeo 10.7.0 by Vimeo.com, Inc.

HANGAD

HANGAD

MAKAKAALAM

MAKAKAALAM

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC