Notizapp mit Widget, formatiertem Text, eingefärbten Buttons, Speichern&Ausgabe Text

  • 5 Antworten
  • Letztes Antwortdatum
M

moppelg

Stamm-User
166
Hallo zusammen,

habe mich als Newbie mal 5 Tage am Stück hingesetzt und mein Gesellenstück zusammengeschustert. Ich werde hier die nächsten Tage [ist nun geschehen Anm.Red.] noch ausführlicher auf die einzelnen Besonderheiten des Programms eingehen.
Habe die einzelnen Stücke alle aus diversen Quellen im Internet zusammengesetzt, also Dank an alle die bisher auch Tutorials und Codes veröffentlich haben.

Hoffe, dass auch der ein oder andere an der App gefallen findet. Ich habe sie mit dem Hintergrund programmiert, weil ich eine Notiz App vermisst habe, die einfach nur von mir Geschriebenes rein als Text auf dem Desktop anzeigt. Die .apk wurde für API Level 7, sprich 2.1 programmiert. Müsste als ganzes aber auch auf API Level 5 laufen. Ob dazu jedoch ein Neukompilieren und Ändern des Manifests nötig ist weiß ich nicht.

Es steht jedem frei das Ding zu verändern und damit zu machen was ihm beliebt. Ich habe auch vor es mal weiterzuentwickeln, so dass in einem Feld einfach Kontakte hinzugefügt werden können (z.B. auch aus dem Adressbuch) die dann unter muss "muss zurückgerufen bzw. angemailt werden). Aber das ist noch Zukunftsmusik. Ich muss jetzt erst einmal liegengelassenes erledigen...

Anleitung zur Benutzung des Widgets:
Das Widget startet mit einem Kästchen Höhe und vier Kästchen Breite unsichtbar auf dem Desktop. Zum Starten dann an die Stelle klicken, an die das Widget gepflanzt wurde und dann im erscheinendem Fenster seinen Text eingeben.

Kleiner Tipp für die hardcore transparenten unter euch, es gibt auch transparente Schrift zur Auswahl, d.h. man kann auch nen Titel benutzen der nicht auf dem Desktop angezeigt wird (die "unsichtbare Widget" Warnung bleibt dann aber [fälschlicherweise:rolleyes:] aus).

Gruß Moppel

P.s.: Man kann über Menü die Widgetschrift anpassen

Codes:
Ich geh einfach mal meinen Code durch und klaub mir das ein oder andere raus. Vieles ist sicher trivial, aber ich hätte mich als Einsteiger drüber gefreut. Für die größeren Zusammenhänge empfiehlt es sich, einen Blick in den Code zu werfen:

Zeigt ne Toast-Messege an. Tauch ne Fehlermeldung auf, muss vermutlich noch was importiert werden:
Code:
Toast.makeText(context, "Achtung, durchsichtiges Notiz-Widget vorhanden...", Toast.LENGTH_SHORT).show();
Lädt sich ne View z.B. vom Widget (Knopf, Text etc.). Diese kann anschließend bearbeitet werden (Hier einen Intend anbinden, Textfarbe und Inhalt ändern):

Code:
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
remoteViews.setOnClickPendingIntent(R.id.button_two, configPendingIntent);
remoteViews.setTextViewText(R.id.button_two, spannedText);    
remoteViews.setTextColor(R.id.button_two, textColor);
Zugriff auf Funktionen die sich in einer eigenen Klasse im selben Package befinden. ClickOneActivity ist dabei meine Klasse.
Code:
String text   = ClickOneActivity.loadTitlePref(context, appWidgetId);
SetSpan formatiert einen Text so, dass er ne ander Farbe, Style oder font erhält:
Code:
String font   = "Hallo, ich werde gleich formatiert";        
SpannableString spannedText = new SpannableString(text);
spannedText.setSpan(new StyleSpan(Typface.BOLT), 0, spannedText.length(), 0);
spannedText.setSpan(new TypefaceSpan("monospace"), 0, spannedText.length(), 0);
Das lässt sich auch anders implementieren:
Code:
String html =  "Alles löschen" + "<br />" + "<small>" + "(Gedrückt halten)" + "</small>"; 
Button deleteall     = (Button) findViewById(R.id.deleteall);
deleteall.setText(Html.fromHtml(html), TextView.BufferType.SPANNABLE);
Weist einem Knopf auf dem Widget einen Intent zu. Diesem gebe ich noch eine "URI", damit er von den gleichen Knöpfen anderer, von mir erstellten, Notiz-Widgets unterschieden werden kann.
Code:
Intent configIntent = new Intent(context,  ClickOneActivity.class );
configIntent.setAction( ACTION_WIDGET_CONFIGURE );
configIntent.putExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId );
Uri data = Uri.withAppendedPath(Uri.parse("ButtonWidget://widget/id/#"+appWidgetId), String.valueOf(appWidgetId));
configIntent.setData(data);
PendingIntent configPendingIntent = PendingIntent.getActivity(context, 0, configIntent, 0);
        
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
remoteViews.setOnClickPendingIntent(R.id.button_two, configPendingIntent);
Nach Aufruf von onUpdate soll jedes Widget geupdatet werden:
Code:
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 
    {
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++) 
        {
            int appWidgetId = appWidgetIds[i];      
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }
Um farbige Buttons zu haben, eruierte ich zwei Wege. Einer ist sich einen Button selber zusammenzustellen in einer xml z.B.: es gibt noch viel mehr Optionen wie Farbgradienten etc. Die xml sollte im drawable Ordner liegen.
Code:
<?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape>
            <solid 
                android:color="@drawable/green"/> 
            <corners
                android:radius="5dp" />
            <padding
                android:left="5dp"
                android:top="10dp"
                android:right="5dp"
                android:bottom="10dp" />
        </shape>
    </item>

    <item
        android:state_pressed="false">
        <shape>
            <solid 
                android:color="@drawable/red"/> 
            <corners
                android:radius="5dp" />
            <padding
                android:left="5dp"
                android:top="10dp"
                android:right="5dp"
                android:bottom="10dp" />
        </shape>
    </item>
</selector>
Da ich das zu mühselig fand, habe ich die Standartbuttons genommen und einen Farblayer drübergelegt. Sollen sie transparent sein, kann dies auch noch hinzugefügt werden:
Transparenz:
Code:
Drawable d;        
d= findViewById(R.id.confirm).getBackground();
d.setAlpha(200);
Farbfilter:
Code:
PorterDuffColorFilter filter;
Drawable d;
d= findViewById(R.id.deleteall).getBackground();  
filter = new PorterDuffColorFilter(Color.argb(100, 255, 0, 0), PorterDuff.Mode.SRC_ATOP);  
d.setColorFilter(filter);
Jedoch ist der Farbfilter auch bei gedrückter Taste aktiv. Damit ich dort aber das normale grün der Standartbuttons habe implementierte ich noch einen Touchlistener auf den gleichen Button, der bei aktivwerden den Filter wegnimmt:

Code:
Button confirmButton = (Button) findViewById(R.id.confirm);
confirmButton.setOnClickListener(mConfirm);  
confirmButton.setOnTouchListener(mConfirmOnTouch);
Code:
private OnTouchListener mConfirmOnTouch = new OnTouchListener() 
    {        
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            if(event.getAction() == MotionEvent.ACTION_DOWN)
            {
                Drawable d = findViewById(R.id.confirm).getBackground();  
                findViewById(R.id.confirm).invalidateDrawable(d);  
                d.clearColorFilter();
            }
            else
            {
                setButtonFilter();
            }
            return false;
        }
    };
Es sei noch dazugesagt, dass diese Methode einen klitzekleinen Schönheitsfehler hat, der jedoch zu 99,9999% nicht auffällt. Wird die Taste gedrückt ist sie im normalen grün. Verlässt man bei gedrücktem Screen schnappt die Farbe wieder zurück, was ja auch soll. Slide ich dann jedoch wieder drauf, wird nicht mehr MotionEven.ACTION_DOWN aufgerufen und der Filter bleibt drinne, was in einer anderen Farbe resultiert. Leider habe ich keinen Event gefunden, die feststellt, wann der eigentliche View, sprich Button verlassen wird. Blöd mit x und y rumrechnen wollte ich auch nicht. Dafür ist der "Fehler" zu unauffällig.

Text und Zahlen speicher ich in der Preferenceclasse ab. Das ist mehr als einfach. Zum speichern:
Code:
SharedPreferences.Editor prefs = context.getSharedPreferences("Einstellungen", 0).edit();
prefs.putString("title" + appWidgetId, text);
prefs.commit();
Die ID füge ich noch an den Speichername an, damit das jeweilige Widget seinen Text oder Int abrufen kann.
Code:
Zum laden des Textes, bzw. ints:
SharedPreferences prefs = context.getSharedPreferences("Einstellungen", 0);
String font = prefs.getString("font" + appWidgetId, "serif");
Meinen AltertDIalog zum abfragen des Schriftfonts habe ich so gestaltet:
Code:
private void dialogStyle() 
     {     
         
         final CharSequence[] items = {"Fett", "Fett & Kursiv", "Kursiv", "Normal"};
         DialogInterface.OnClickListener mOnClick = new DialogInterface.OnClickListener()
         {
                @Override
                public void onClick(DialogInterface dialog, int item) 
                {
                     final Context context = ClickOneActivity.this;
                     SharedPreferences.Editor prefs = context.getSharedPreferences("Einstellungen", 0).edit();
                     switch (item) 
                     {    
                         case 0: prefs.putInt("style" + mAppWidgetId, Typeface.BOLD); break;
                         case 1: prefs.putInt("style" + mAppWidgetId, Typeface.BOLD_ITALIC); break;
                         case 2: prefs.putInt("style" + mAppWidgetId, Typeface.ITALIC); break;
                         case 3: prefs.putInt("style" + mAppWidgetId, Typeface.NORMAL); break;
                      }                       
                     prefs.commit();
                     Toast.makeText(context, "\"" + items[item] + "\" gewählt", Toast.LENGTH_SHORT).show();
                }          
          }; 
         AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setTitle("Wähle einen Widgetschriftstyle:");
         builder.setItems(items, mOnClick);
        
         AlertDialog alert = builder.create();
         alert.show();
    }
zum Laden des gespeicherten wie oben erwähnt:
Code:
SharedPreferences prefs = context.getSharedPreferences("Einstellungen", 0);
String font = prefs.getString("font" + appWidgetId, "serif");
Ich vermute mal, dass das in der Preference gespeicherte beim löschen des Widgets auch entfernt werden soll. Daher das überschreiben von OnDestroy:
Code:
@Override
    public void onDeleted(Context context, int[] appWidgetIds) 
    {
        Toast.makeText(context, "Deleting Widget...", Toast.LENGTH_SHORT).show();
        // When the user deletes the widget, delete the preference associated with it.
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++) 
        {
            ClickOneActivity.deleteTitleAndBodyPref(context, appWidgetIds[i]);
        }
    }
Ich habe mich immer geärgert, dass wenn die Hometaste gedrückt wird die Activity nicht geschlossen wird. Denn bleibt sie offen und ich drücke andere Widgets vom Typ des NotiWidgets, dann öffnen sich diese Fenster. Drücke ich dann "zurück", lande ich plötzlich im alten Fenster und visa versa. Auf jeden Fall Käse. Einen finish() in onPause ist aber auch keine Lösung, weil bei der ScreenRotation diese aufgerufen wird. Dann möchte ich nicht, dass die Activity geschlossen wird. Geholfen hat folgender Eintrag im Manifest der Activity:
android:configChanges="orientation">
Dann wird beim Screenwechseln nicht onPause aufgerufen und ich kann dort in Ruhe mein finish() reinschreiben:
Code:
@Override
     protected void onPause() 
     {
            super.onPause();
            final Context context = ClickOneActivity.this;  
            saveTitlePref( context, mAppWidgetId, mTitleText.getText().toString());
            saveBodyPref(  context, mAppWidgetId,  mBodyText.getText().toString());
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
            ButtonWidget.updateAppWidget(context, appWidgetManager, mAppWidgetId);
            finish();
      }
So sieht das bei mir im Code auf, ich speicher alles ab und update das Widget.

Vollständigkeithalber noch wie ich das Optionsmenü aufrufe:
public boolean onCreateOptionsMenu(Menu menu)
Code:
   {
        super.onCreateOptionsMenu(menu);
        MenuInflater mi =
        new MenuInflater(getApplication());
        mi.inflate(R.menu.menu, menu);
        return true; 
    }
    
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        switch (item.getItemId()) 
        {        
        case R.id.schriftfarben: 
            dialogSchriftfarben();
            return true;
        case R.id.schriftstyle: 
            dialogStyle();
            return true;
        case R.id.schriftfont: 
            dialogFont();
            return true;
        }
        return super.onContextItemSelected(item);
    }
Die XML dazu:
Code:
<?xml version="1.0" encoding="utf-8"?>
<menu
      xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/schriftfarben"
        android:title="Schriftfarbe"
        />
    <item
        android:id="@+id/schriftstyle"
        android:title="Schriftstyle"/>
        />
    <item
        android:id="@+id/schriftfont"
        android:title="Schriftfont"/>
        />
</menu>
Damit das Programm die Wallpaper im Hintergrund hat, hier noch mein Manifesteintrag:
Code:
<activity android:name=".ClickOneActivity"                  
                  android:theme="@style/Theme.Wallpaper"
                  android:configChanges="orientation">
            <intent-filter>
                <action android:name="de.thesmile.android.widget.buttons.ButtonWidget.ACTION_WIDGET_CONFIGURE"/>
            </intent-filter>
        </activity>
Dazu wird noch die styles.xml benötigt im values Ordner:
Code:
<resources>
    <!-- Base application theme is the default theme. -->
    <style name="Theme" parent="android:Theme">
    </style>

    <style name="Theme.Black">
        <item name="android:windowBackground">@drawable/screen_background_black</item>
    </style>

    <style name="Theme.Wallpaper" parent="android:style/Theme.Wallpaper">
        <item name="android:colorForeground">#fff</item>
    </style>

    <style name="Theme.Translucent" parent="android:style/Theme.Translucent">
        <item name="android:windowBackground">@drawable/translucent_background</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:colorForeground">#fff</item>
    </style>

    <style name="Theme.Transparent">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
        <item name="android:windowBackground">@drawable/transparent_background</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:colorForeground">#fff</item>
    </style>
    
    <style name="TextAppearance.Theme.PlainText" parent="android:TextAppearance.Theme">
        <item name="android:textStyle">normal</item>
    </style>
    
</resources>
In der XML oben sind auch noch andere Themes, die alternativ benutzt werden könnten (der scharfsinnige Leser wird sehen, dass die Beispiele von Google sind:)

Die Titel Leiste in der Activity bekomme ich weg mit:
Code:
@Override
    protected void onCreate(Bundle icicle) 
    {
        super.onCreate(icicle);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
...
Trotz dem sind mir noch zwei Bugs bekannt, vielleicht weiß ja jemand Abhilfe:
- Wenn ich bei Details ins Textfeld nur eine E-Mailadresse reinschreibe und die nach Wiederaufruf der Acktivity blau markiert wird ist es nahezu ein Ding der Unmöglichkeit irgendwie in das Textfeld zu klicken um Text hinzuzufügen. Der Link wird immer magisch angeklickt.
- Nach löschen aller Widgets vom Homescreen kann je nach Fall noch in der History eine Activity erscheinen. Bei Aufruf dieser lässt sich wieder Text eingeben und speichern, obwohl das dazugehörige Widget weg ist. Gibt es ne Möglichkeit eine App aus der History rauszunehmen?

!Achtung!
Es kann zum Verlust/Absturz der Notiz kommen kann, wenn bei "Titel" eine lange Nummer oder E-Mailadresse eingegeben wird. So war es bei mir zumindest unter Home++. Ich vermute, dass es damit zusammenhängt, dass ich für das Textfeld ebenso wie beim Detailtextfeld die automatische Telefonnummern- und Emailadressenerkennung aktiviert habe. Warum das beim einen Feld funktioniert und beim anderen nicht ist mir dabei unklar. Hoffe, dass ihr nicht schon vorher die böse Erfahrung gemacht haben... . Wenn es stört bitte bei euch dann selber im Quellcode korrigieren und Neukompilieren
 

Anhänge

  • NotizWidget.zip
    87,3 KB · Aufrufe: 430
  • NotizWidget.apk
    37,2 KB · Aufrufe: 478
  • Beispiel2.JPG
    Beispiel2.JPG
    167 KB · Aufrufe: 1.108
  • Beispiel1.jpg
    Beispiel1.jpg
    39,7 KB · Aufrufe: 885
Zuletzt bearbeitet:
  • Danke
Reaktionen: balg, myLG, PauleFlügge und 3 andere
Sehr nett! Danke für das gute Beispiel!
 
  • Danke
Reaktionen: Chrischi2809 und moppelg
Habe es auf meinem HTC Desire installiert.
Wenn ich das Widget auf dem Destkop anlege, ist das widget durchsichtig. Auf dem desktop ist nichts zu sehen... klicke einfach ins leere bis es sich öffnet...
 
Hey Chrischi, danke, dass du es nochmal ansprichst. War etwas unklar dokumentiert von mir.
Wie Chrischi bereits sagte startet das Widget mit einem Kästchen Höhe und vier Kästchen Breite unsichtbar auf dem Homescreen. Zum Starten dann an die Stelle klicken, an die das Widget gepflanzt wurde und seinen Text eingeben.
Es sind auch mehrere Widgets möglich.

Kleiner Tipp für die hardcore transparenten unter euch, es gibt auch transparente Schrift zur Auswahl, d.h. man kann auch nen Titel benutzen der nicht auf dem Desktop angezeigt wird (die "unsichtbare Widget" Warnung bleibt dann aber [fälschlicherweise:rolleyes:] aus).
 
Zuletzt bearbeitet:
Nice genau sowas hab ich gesucht :D
Sach mal ist es noch möglich eine Funktion für Fotos ein zu fügen?
Sprich dass man zu seiner Notiz ein Foto machen kann und es klein auf der rechten oder linken Seite angezeigt wird :)
Und wenn man auf das Bild drückt, dass es in einem "popup" in groß angezeigt wird.
 
Hi Mischor,
danke für dein Interesse, aber leider weiß ich nicht wann ich mich mal wieder ransetze um mit Android weiterzuprogrammieren.

!Achtung!
Möchte aber noch meine Antwort nutzen um eine kleine Warnung rauszugeben, dass es zum Verlust/Absturz der Notiz kommen kann, wenn bei "Titel" eine lange Nummer oder E-Mailadresse eingegeben wird. So war es bei mir zumindest unter Home++. Ich vermute, dass es damit zusammenhängt, dass ich für das Textfeld ebenso wie beim Detailtextfeld die automatische Telefonnummern- und Emailadressenerkennung aktiviert habe. Warum das beim einen Feld funktioniert und beim anderen nicht ist mir dabei unklar. Hoffe, dass ihr nicht schon vorher die böse Erfahrung gemacht haben...
 

Ähnliche Themen

wernho
Antworten
11
Aufrufe
1.125
wernho
wernho
S
  • StudioSchmith
Antworten
9
Aufrufe
1.648
jogimuc
J
T
Antworten
3
Aufrufe
1.217
jogimuc
J
Zurück
Oben Unten