2013年3月18日月曜日

マーキーとTextView

現在の4.x系ではどうかちょっと調べてないのですが、 前に練習向けに作ってたアプリで、TextViewのマーキー動作が以下の仕様のため、思ったように動かせませんでした。

  • フォーカスが当たっていないと動作しない
  • テキストの長さがViewの幅以上でないと動かない

このため、マーキー動作を行うTextViewを自作したので、公開しておきます。 ・・・本当は1,2年位前に作ったのですが、書くのを怠けてました(´ω`)


で、最初はTextViewのソースから、そっくりそのままマーキー部分を改造したものを作ろうと したのですが、TextViewの内部のパッケージを利用している部分がどうにもならなかったので 断念しました。。。

そのため、TextViewを継承して、マーキー動作を行う部分をTextViewから移植することで なんとか思った動作が実現できました。 できたソースは以下になります。


import java.lang.ref.WeakReference;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;

public class MarqueeTextView extends TextView {
    private Marquee mMarquee;
    private int repeatLimit = 0;
    private boolean repeatInfy = false;
    private boolean repeatReverse = false;
    private float marqueeSpeed = 0;
    private boolean isConfigureChange = false;
    
    public MarqueeTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ViewTreeObserver observer = this.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // 画面方向が変更された際にマーキーをやり直す
                if(isConfigureChange){
                    if(mMarquee!=null) {
                        stopMarquee();
                        startMarqueeAndRetry();
                    }
                    isConfigureChange = false;
                }
            }
        });
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        // Draw the background for this view
        // 描画時に座標をずらすことでマーキー動作を行っている
        if (mMarquee != null && mMarquee.isRunning()) {
            this.scrollTo((int) mMarquee.mScroll, 0);
        }
        super.onDraw(canvas);
    }

    @Override
    protected float getLeftFadingEdgeStrength() {
        if (mMarquee != null && !mMarquee.isStopped()) {
            final Marquee marquee = mMarquee;
            return marquee.mScroll / getHorizontalFadingEdgeLength();
        }
        return super.getLeftFadingEdgeStrength();
    }
    
    @Override
    protected float getRightFadingEdgeStrength() {
        if (mMarquee != null && !mMarquee.isStopped()) {
            final Marquee marquee = mMarquee;
            return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
        }
        return super.getRightFadingEdgeStrength();
    }
    
    @Override
    public void onConfigurationChanged(Configuration newConfig){
        // 画面方向の変更を検知
        super.onConfigurationChanged(newConfig);
        isConfigureChange = true;
    }
    
    /**
     * setTextがfinalのため、Textセット用のメソッドを配置
     */
    public void setMarqueeText(CharSequence text){
        super.setText(text);
        mMarquee.refreshMarqueeParams();
    }
    
    /**
     * テキストがView幅以上か識別する
     */
    public boolean isOverTextWidth(){
        if(this.getLayout()!=null){
            return (this.getLayout().getLineWidth(0) > this.getWidth());
        }
        else{
            return false;
        }
    }

    /**
     * マーキー動作をリピート回数を設定する
     */
    public void setRepeatLimit(int limit){
        if(limit < 0){
            limit = 0;
        }
        repeatLimit = limit;
    }
    
    /**
     * マーキー動作を無制限でリピートする
     */
    public void setRepeatInfy(boolean infy){
        repeatInfy = infy;
        if(mMarquee!=null){
            mMarquee.setRepeatInfy(repeatInfy);
        }
    }
    
    /**
     * マーキー動作を右端まできたら折り返すような動作にする
     */
    public void setRepeatReverse(boolean setReverse){
        repeatReverse = setReverse;
        if(mMarquee!=null){
            mMarquee.setRepeatReverse(setReverse);
        }
    }
    
    /**
     * マーキー動作の速度を設定する
     */
    void setMarqueeSpeed(float marqueeSpeed){
        this.marqueeSpeed = marqueeSpeed;
        if(mMarquee!=null){
            mMarquee.setMarqueeSpeed(marqueeSpeed);
        }
    }

    /**
     * マーキー動作を開始する
     */
    public void startMarquee() {
        if ((mMarquee == null || mMarquee.isStopped())) {
            if (mMarquee == null) mMarquee = new Marquee(this);
            mMarquee.setMarqueeSpeed(marqueeSpeed);
            mMarquee.setRepeatReverse(repeatReverse);
            mMarquee.setRepeatInfy(repeatInfy);
            if(repeatInfy){
                mMarquee.start(1);
            }
            else{
                mMarquee.start(repeatLimit);
            }
        }
    }
    
    /**
     * テキスト幅がView以上の場合にマーキー動作を開始する
     */
    public void startMarqueeIfTextWidthLarger(){
        if(isOverTextWidth()){
            startMarquee();
        }
    }

    /**
     * マーキー動作を開始する
     */
    private void startMarqueeAndRetry(){
        if ((mMarquee == null || mMarquee.isStopped())) {
            if (mMarquee == null) mMarquee = new Marquee(this);
            mMarquee.setMarqueeSpeed(marqueeSpeed);
            mMarquee.setRepeatReverse(repeatReverse);
            mMarquee.setRepeatInfy(repeatInfy);
            if(repeatInfy){
                mMarquee.startAndRetry(1);
            }
            else{
                mMarquee.startAndRetry(repeatLimit);
            }
        }
    }
    
    /**
     * マーキー動作を停止する
     */
    public void stopMarquee() {
        if (mMarquee != null && !mMarquee.isStopped()) {
            mMarquee.stop();
        }
    }
    
    /**
     * マーキー動作用の内部クラス
     * オリジナルのTextViewクラスから抜粋し、一部を改造しました。
     */
    final class Marquee extends Handler {
        // TODO: Add an option to configure this
        // オリジナルのソースから抜粋したのでいらない定数があるかも。。。
        // マーキーの最後の方で、テキストの頭がでてくる動作ができない。
        // コピーしたViewを用意して最後辺りで頭を流すとか?
//        private static final float MARQUEE_DELTA_MAX = 0.07f;
        private static final int MARQUEE_DELAY = 1200;
        private static final int MARQUEE_RESTART_DELAY = 1200;
        private static final int MARQUEE_RETRY_DELAY = 1200;
        private static final int MARQUEE_RESOLUTION = 1000 / 30;
        private static final int MARQUEE_PIXELS_PER_SECOND = 30;

        private static final byte MARQUEE_STOPPED = 0x0;
        private static final byte MARQUEE_STARTING = 0x1;
        private static final byte MARQUEE_RUNNING = 0x2;
        private static final byte MARQUEE_RETRYING = 0x3;

        private static final int MESSAGE_START = 0x1;
        private static final int MESSAGE_TICK = 0x2;
        private static final int MESSAGE_RESTART = 0x3;
        private static final int MESSAGE_RETRY = 0x4;

        private final WeakReference mView;

        private byte mStatus = MARQUEE_STOPPED;
        private float mScrollSpeed;
        private float mMaxScroll;
//        float mMaxFadeScroll;
//        private float mGhostStart;
//        private float mGhostOffset;
//        private float mFadeStop;
        private int mRepeatLimit;
        private boolean mRepeatReverse;
        private boolean mRepeatInfy;
        private boolean reverse;

        float mScroll;

        Marquee(TextView v) {
            final float density = v.getContext().getResources().getDisplayMetrics().density;
            mScrollSpeed = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
            mView = new WeakReference(v);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_START:
                    mStatus = MARQUEE_RUNNING;
                    tick();
                    break;
                case MESSAGE_TICK:
                    tick();
                    break;
                case MESSAGE_RETRY:
                    retry();
                    break;
                case MESSAGE_RESTART:
                    if (mStatus == MARQUEE_RUNNING) {
                        TextView text = mView.get();
                        if(text!=null){
                            text.scrollTo(0, 0);
                        }
                        if(mRepeatInfy){
                            start(1);
                        }
                        else{
                            if (mRepeatLimit >= 0) {
                                mRepeatLimit--;
                            }
                            start(mRepeatLimit);
                        }
                    }
                    break;
            }
        }

        void tick() {
            if (mStatus != MARQUEE_RUNNING) {
                return;
            }

            removeMessages(MESSAGE_TICK);

            TextView textView = mView.get();
            if (textView != null) {
                if(reverse){
                    mScroll -= mScrollSpeed;
                    if (mScroll < 0) {
                        mScroll = 0;
                        reverse = !reverse;
                        sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
                    } else {
                        sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
                    }
                }
                else{
                    mScroll += mScrollSpeed;
                    if (mScroll > mMaxScroll) {
                        mScroll = mMaxScroll;
                        if(mRepeatReverse){
                            reverse = !reverse;
                            sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESTART_DELAY);
                        }
                        else{
                            sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
                        }
                    } else {
                        sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
                    }
                }
                textView.invalidate();
            }
            else {
                stop();
            }
        }

        void stop() {
            mStatus = MARQUEE_STOPPED;
            removeMessages(MESSAGE_START);
            removeMessages(MESSAGE_RESTART);
            removeMessages(MESSAGE_RETRY);
            removeMessages(MESSAGE_TICK);
            resetScroll();
        }

        private void resetScroll() {
            mScroll = 0.0f;
            final TextView textView = mView.get();
            if (textView != null) {
                textView.scrollTo(0, 0);
                textView.invalidate();
            }
        }
        
        private void retry(){
            removeMessages(MESSAGE_RETRY);
            final TextView textView = mView.get();
            if(textView.getLayout()==null){
                mStatus = MARQUEE_RETRYING;
                sendEmptyMessageDelayed(MESSAGE_RETRY, MARQUEE_RETRY_DELAY);
            }
            else if(textView.getLayout().getLineWidth(0) > textView.getWidth()){
                   start(mRepeatLimit);
            }
        }
        
        private void startAndRetry(int repeatLimit){
            mRepeatLimit = repeatLimit;
            retry();
        }

        void start(int repeatLimit) {
            if (repeatLimit == 0) {
                stop();
                return;
            }
            mRepeatLimit = repeatLimit;
            final TextView textView = mView.get();
            if (textView != null) {
                mStatus = MARQUEE_STARTING;
                mScroll = 0.0f;
                // テキストの幅
                final int textWidth = (int)textView.getLayout().getLineWidth(0);
                // TextViewの幅
                final float lineWidth = textView.getWidth();
                // スクロールする右端の座標
                // 以下のソースでは、
                // lineWidth/3 になっているのでView幅の 1/3 までテキストの右端がスクロールしたところでMarqueeが終了する
                final float gap = lineWidth / 3.0f;
//                mGhostStart = lineWidth - textWidth + gap;
//                mMaxScroll = mGhostStart + textWidth;
                mMaxScroll = textWidth - gap;
//                mGhostOffset = lineWidth + gap;
//                mFadeStop = lineWidth + textWidth / 6.0f;
//                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
                textView.invalidate();
                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
            }
        }
        
        void refreshMarqueeParams(){
            TextView textView = mView.get();
            mStatus = MARQUEE_STARTING;
            mScroll = 0.0f;
            int textWidth = (int)textView.getLayout().getLineWidth(0);
            float lineWidth = textView.getWidth();
            float gap = lineWidth / 3.0f;
//            mGhostStart = lineWidth - textWidth + gap;
//            mMaxScroll = mGhostStart + textWidth;
            mMaxScroll = textWidth - gap;
//            mGhostOffset = lineWidth + gap;
//            mFadeStop = lineWidth + textWidth / 6.0f;
//            mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
        }

        // この辺のGhostXXXは役割がわからなかった。
//        float getGhostOffset() {
//            return mGhostOffset;
//        }
//
//        boolean shouldDrawLeftFade() {
//            return mScroll <= mFadeStop;
//        }
//
//        boolean shouldDrawGhost() {
//            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
//        }

        boolean isRunning() {
            return mStatus == MARQUEE_RUNNING;
        }

        boolean isStopped() {
            return mStatus == MARQUEE_STOPPED;
        }
        
        boolean isRetryed(){
            return mStatus == MARQUEE_RETRYING;
        }
        
        void setRepeatReverse(boolean setReverse){
            mRepeatReverse = setReverse;
        }
        
        void setRepeatInfy(boolean infy){
            mRepeatInfy = infy;
        }
        
        void setMarqueeSpeed(float marqueeSpeed){
            if(marqueeSpeed <= 0){
                final float density = mView.get().getContext().getResources().getDisplayMetrics().density;
                mScrollSpeed = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
                return;
            }
            if(marqueeSpeed < 0.1){
                mScrollSpeed = 0.1f;
            }
            else {
                mScrollSpeed = marqueeSpeed;
            }
        }
    }

}


で、使用する場合はこんな感じ


MarqueeTextView mtext = (MarqueeTextView) view.findViewById(R.id.text);
mtext.setRepeatInfy(true);
mtext.startMarqueeIfTextWidthLarger();

上記はマーキー回数無制限でテキストがView幅より大きい場合マーキー動作が行われます。 注意しないといけないのが、onPauseされたときに止めないと裏で動き続けます。。。

あと、TextViewを継承しているので、 singleLine="true"、ellipsize="marquee"で使った方がいいです。
というか上記の設定以外で使ったことがない。。。

2013/3/30追記
そういえば上の使ってマーキー動かした状態でフォーカス与えて オリジナルのマーキー動作した場合ってどうなるんだ?
・・・そうだ、見なかったことにしよう。

久々の更新

結構長い間放置してたけど久々に更新。
もう半年くらい前になるけど、 google playにアプリを公開してみました。
https://play.google.com/store/apps/details?id=com.mfmf.playlisteditor
PlaylistEditor とかいうM3U形式のプレイリストを作成、編集するアプリです。
特徴はプレイリストを編集するときのソート機能が微妙に充実してるくらいです。 まぁ自分用に作成したアプリですが、よろしければお使いください。

2011年10月25日火曜日

進捗その3

ううむ、中々進みません。。。 取りあえず現在のところこんな感じ。

1.接続画面

まず接続先登録で、接続先のPCのアドレス、ポートを登録します。 すると、真ん中に登録したものが出るので、それをタップすると接続が開始されます。 接続先PCに別途作成したサーバプログラムを起動しておく必要があります。

2.接続先PCの表示

2.1.ファイルリスト表示

PCに接続すると、サーバ側で設定したデフォルトディレクトリが表示されます。 対応する画像ファイルをタップすると、アプリ内のビューワで画像が閲覧できます。 ちなみに上部のパスを直接編集して移動もできます。

2.2.圧縮ファイルの表示

対応する圧縮ファイル(ZIP/RAR)をタップすると、中身のファイルが閲覧できます。 ここで、2.1.と同じように画像ファイルをタップすると、そのまま画像が閲覧可能です。

ちなみに、自作した専用サーバを使う理由はこの為です。 sambaとかcifsとか扱うJavaのjcifsってライブラリを使えば、ファイルリスト表示とかダウンロード/アップロードは可能ですが、 圧縮ファイルを一旦ローカルにダウンロードしないと閲覧することができません。 ZipFileクラスのコンストラクタが、Fileクラスとかローカルパスしか受け付けないのでしょうがないです。 ので作成したサーバプログラムでは、この辺の処理をサーバ側で行って、その結果を返却してくれます。

2.3.ファイルリストのフィルタ

左上のPCっぽいアイコンをタップすると、虫眼鏡マークに切り替わるので ここで文字を入力して、右のボタンをタップすると、ファイルリスト内の要素をフィルタリングできます。

2.4.ファイルメニュー

リストの要素を長押しでメニューが表示され、他のアプリで開くことができます。 候補に出るアプリは、 1.接続画面でチラッと見えた設定に、拡張子とコンテントタイプの対応設定があるので そっから候補が選出されます。 他のアプリで開く場合、接続先PCのファイルは、アプリ用のディレクトリに一時ファイルとしてダウンロードしてから 開かれるのでPDFとかも開くことが可能です。

3.ローカル(Android)内の表示

右下にあるAndroidっぽいボタンと、PCっぽいボタンで 接続先PCの表示とAndroidの表示を切り替えることができます。 操作感は2.接続先PC と同じ感じです。

4.ローカルと接続先PC

4.1.デュアル表示

右下のアイコンの切り替え方によっては、 左右にローカルと接続先PCを表示することができます。

4.2.アップロード/ダウンロード

デュアル表示されている場合、メニューにアップロード/ダウンロードがでてきます。 これを選択すると、現在表示されているディレクトリに選択したファイルがコピーされます。

5.音楽ファイル再生

ファイルリストの音楽ファイルをタップすると、音楽ファイルが再生されます。自前で作成したプレーヤーです。 このとき、同ディレクトリ内の他の音楽ファイルも一緒にプレイリストに入り、連続再生されます。

取りあえずこんな感じになってます。ネットワーク先のPCのZIP/RARを直で閲覧できるので、結構使えてます。 ユーザ認証とか暗号化とかしていないので超ノーガードですが、まぁ、ローカル内で使うにはいいか、って感じになってるので後回し。

2011年10月2日日曜日

EditableなSpinner

1つ前の投稿で作成した拡張子とContent-TypeのDBを 利用するためにこんな↓感じの画面を加えました。  

ここから、新しく追加、変更を設定するダイアログで、 直接入力が可能なSpinnerを作ってみようと思いました。

で、できたのがコレ↓


ちょっと画像ではわかりにくいですね。
作成方法はSpinnerに設定するアダプタに対して

作成時、EditTextのレイアウトを食わせる
setDropDownViewResourceでテキストのレイアウトを設定する

な感じの操作をする。
具体的にはこんな感じ。

    //Spinnerの設定
    Spinner spn = (Spinner)inputView.findViewById(id.spinnerType);

    //Spinnerに設定するアダプタのレイアウトをEditTextのレイアウトで作成する
    //指定している [dialog_spinner_layout]はホントにEditTextだけのレイアウト
    ArrayAdapter adp = ArrayAdapter.createFromResource(parentAct.getApplicationContext(),
            R.array.MimeTypeArray,R.layout.dialog_spinner_layout);

    //ドロップダウンのレイアウトをTextView?のレイアウトで作成する
    // android.R.layout.simple_dropdown_item_1line はandroid側で定義済みのレイアウト
    // 普通にTextViewを指定するとドロップダウンで表示されたものがはみ出ることがある
    adp.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);

    spn.setAdapter(adp);

取りあえず一応できました。
が、細かいところの設定が色々難しい。

初期値として、EditTextの文字列を変更させようかと思ったのですが、どうやっても初期化時に、EditTextが取得できない。
表示後なら、
spinner.getSelectedView();
でEditTextを取得できるのですが。。。

やっぱり素直にAutoCompleteTextViewクラスで作成するべき、というのが今回の結論でした。

2011年9月22日木曜日

暗黙のインテントとContent-Type

色々と躓きながら作成しています。
今回は、暗黙のインテントを飛ばす時に 拡張子でContent-Typeを色々指定する必要があるのですが、 ifとかで判別して一々やるのはめんどいなぁってことで、 ちょっとしたDBとクラスを作成してみました。 拡張子とContent-Typeを紐付けしたDBを作成して、 暗黙のインテントを飛ばす時にDBからContent-Typeを取得してやろうという試みです。
まずは拡張子とContent-Typeを紐付けたDBのソースです。

public class ContentTypeDB {
 /**
  * DBの要素
  * _EXT:拡張子
  * _TYPE:content-type文字列
  */
 public interface DataColumns extends BaseColumns {
  public static final String DISPLAY_NAME ="_display_name";
  public static final String _EXT = "_EXTENSION";
  public static final String _TYPE = "_TYPE";
 }
 
 private SQLiteDatabase contentTypeDB;
 private static final String DATABASE_TABLE = "IntentParamTable";
 
 private static class DatabaseHelper extends SQLiteOpenHelper {
  private static final String DATABASE_NAME = "IntentParamList";
  private static final int DATABASE_VERSION =1;
  //初期化判別用
  private boolean notInitialize = false;
  
  private static final String DATABASE_CREATE = 
   "create table " + 
   DATABASE_TABLE + " (" +
   DataColumns._EXT + " text primary key, " +
   DataColumns._TYPE + " text not null" +
   ");";
   
  public DatabaseHelper(Context context) {
   super(context, DATABASE_NAME, null, DATABASE_VERSION);
  }
  
  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL(DATABASE_CREATE);
   //DBがない場合、trueにしておく
   notInitialize = true;
  }
  
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   db.execSQL("DROP TABLE IF EXIST IntentParamList");
   onCreate(db);
  }
  
  /**
   * DBの初期化を行う
   * @param content
   */
  public void initialize(ContentTypeDB content){
   if(notInitialize){
    //DBを作成した場合、デフォルトのContent-Typeを設定する
    ContentTypeHelper.setDefaultContentType(content);
   }
  }
 }

 /**
  * コンストラクタ
  * @param context
  */
 public ContentTypeDB(Context context){
  DatabaseHelper dbHelper = new DatabaseHelper(context);
  contentTypeDB = dbHelper.getWritableDatabase();
  //DBの初期化を試みる
  dbHelper.initialize(this);
 }
 
 /**
  * 指定要素を削除する
  * @param ipAddress
  * @return
  */
 public int delete(ContentElement elem){
  int count = contentTypeDB.delete(DATABASE_TABLE,
    DataColumns._EXT + " = '" + elem.getExtension()+"'",null);
  return count;
 }
 
 /**
  * 要素を追加する
  * @param element
  * @return
  */
 public int registerElement(ContentElement element){
  if(!existElement(element.getExtension())){
   insert(element);
  }
  return 0;
 }
 
 /**
  * DB内の要素を全て取得する
  * @return
  */
 public ArrayList getAllElements(){
  ArrayList elements = new ArrayList();
  Cursor c = query(null, null, null, DataColumns._EXT + " asc");
  if(c.moveToFirst()){
   do {
    elements.add(new ContentElement(
      c.getString(c.getColumnIndex(DataColumns._EXT)),
      c.getString(c.getColumnIndex(DataColumns._TYPE))
      ));
   } while(c.moveToNext());
  }
  return elements;
 }
 
 public void close(){
  contentTypeDB.close();
 }
 
 public Cursor query(String[] projection, String selection,
   String[] selectionArgs, String sortOrder) {
  SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
  sqlBuilder.setTables(DATABASE_TABLE);
  
  if(sortOrder == null || sortOrder == ""){
   sortOrder = DataColumns._EXT;
  }
  
  Cursor c = sqlBuilder.query(contentTypeDB, projection, selection,
    selectionArgs, null, null, sortOrder);
  return c;
 }
 
 /**
  * DBを拡張子で検索し、Content-Type文字列を取得する
  * @param extension 検索対象の拡張子文字列
  * @return 見つかったContent-Type文字列。DBにない場合nullが返却される。
  */
 public String getType(String extension){
  Cursor c = query(null, DataColumns._EXT+"= \""+extension+"\"", null, null);
  String retType = null;
  if(c.moveToFirst()){
   retType = c.getString(c.getColumnIndex(DataColumns._TYPE));
  }
  return retType;
 }
 
 /**
  * トランザクション開始
  */
 public void startTransaction(){
  contentTypeDB.beginTransaction();
 }
 
 /**
  * コミット
  */
 public void commit(){
  contentTypeDB.setTransactionSuccessful();
 }
 
 /**
  * トランザクション終了
  */
 public void endTransaction(){
  contentTypeDB.endTransaction();
 }

 /**
  * DB内に指定要素が存在するか確認する
  * @param extension
  * @return
  */
 private boolean existElement(String extension){
  boolean exist = false;
  String [] targetCol = {DataColumns._EXT};
  Cursor c = query(targetCol, 
    DataColumns._EXT +"= \""+ extension+"\"", null, null);
  if(c.moveToFirst()){
   exist = true;
  }
  c.close();
  return exist;
 }

 private long insert(ContentElement element){
  long rowID = contentTypeDB.insert(DATABASE_TABLE, "", element.getContentValues());
  if(rowID>0){
   return rowID;
  }
  throw new SQLException();
 }
}


このContentTypeDBクラスのインスタンスを作成することでDBを作成します。 ここで、DBのCreate時にContentTypeHelperクラスを使ってデフォルトの値を DBに突っ込んでます。
ContentTypeHelperクラスのソースはこちらです。

public class ContentTypeHelper {
 //拡張子とContent-Typeのデフォルト値
 static final String[][] defaultTypeArray = {
  {"application/envoy", "evy"},
  {"application/fractals", "fif"},
  {"application/futuresplash", "spl"},
  {"application/hta", "hta"},
  {"application/internet-property-stream", "acx"},
  {"application/mac-binhex40", "hqx"},
  {"application/msword", "doc"},
  {"application/msword", "dot"},
  {"application/octet-stream", "bin"},
  {"application/octet-stream", "class"},
  {"application/octet-stream", "dms"},
  {"application/octet-stream", "exe"},
  {"application/octet-stream", "lha"},
  {"application/octet-stream", "lzh"},
  {"application/oda", "oda"},
  {"application/olescript", "axs"},
  {"application/pdf", "pdf"},
  {"application/pics-rules", "prf"},
  {"application/pkcs10", "p10"},
  {"application/pkix-crl", "crl"},
  {"application/postscript", "ai"},
  {"application/postscript", "eps"},
  {"application/postscript", "ps"},
  {"application/rtf", "rtf"},
  {"application/set-payment-initiation", "setpay"},
  {"application/set-registration-initiation", "setreg"},
  {"application/vnd.ms-excel", "xla"},
  {"application/vnd.ms-excel", "xlc"},
  {"application/vnd.ms-excel", "xlm"},
  {"application/vnd.ms-excel", "xls"},
  {"application/vnd.ms-excel", "xlt"},
  {"application/vnd.ms-excel", "xlw"},
  {"application/vnd.ms-outlook", "msg"},
  {"application/vnd.ms-pkicertstore", "sst"},
  {"application/vnd.ms-pkiseccat", "cat"},
  {"application/vnd.ms-pkistl", "stl"},
  {"application/vnd.ms-powerpoint", "pot"},
  {"application/vnd.ms-powerpoint", "pps"},
  {"application/vnd.ms-powerpoint", "ppt"},
  {"application/vnd.ms-project", "mpp"},
  {"application/vnd.ms-works", "wcm"},
  {"application/vnd.ms-works", "wdb"},
  {"application/vnd.ms-works", "wks"},
  {"application/vnd.ms-works", "wps"},
  {"application/winhlp", "hlp"},
  {"application/x-bcpio", "bcpio"},
  {"application/x-cdf", "cdf"},
  {"application/x-compress", "z"},
  {"application/x-compressed", "tgz"},
  {"application/x-cpio", "cpio"},
  {"application/x-csh", "csh"},
  {"application/x-director", "dcr"},
  {"application/x-director", "dir"},
  {"application/x-director", "dxr"},
  {"application/x-dvi", "dvi"},
  {"application/x-gtar", "gtar"},
  {"application/x-gzip", "gz"},
  {"application/x-hdf", "hdf"},
  {"application/x-internet-signup", "ins"},
  {"application/x-internet-signup", "isp"},
  {"application/x-iphone", "iii"},
  {"application/x-javascript", "js"},
  {"application/x-latex", "latex"},
  {"application/x-msaccess", "mdb"},
  {"application/x-mscardfile", "crd"},
  {"application/x-msclip", "clp"},
  {"application/x-msdownload", "dll"},
  {"application/x-msmediaview", "m13"},
  {"application/x-msmediaview", "m14"},
  {"application/x-msmediaview", "mvb"},
  {"application/x-msmetafile", "wmf"},
  {"application/x-msmoney", "mny"},
  {"application/x-mspublisher", "pub"},
  {"application/x-msschedule", "scd"},
  {"application/x-msterminal", "trm"},
  {"application/x-mswrite", "wri"},
  {"application/x-netcdf", "cdf"},
  {"application/x-netcdf", "nc"},
  {"application/x-perfmon", "pma"},
  {"application/x-perfmon", "pmc"},
  {"application/x-perfmon", "pml"},
  {"application/x-perfmon", "pmr"},
  {"application/x-perfmon", "pmw"},
  {"application/x-pkcs12", "p12"},
  {"application/x-pkcs12", "pfx"},
  {"application/x-pkcs7-certificates", "p7b"},
  {"application/x-pkcs7-certificates", "spc"},
  {"application/x-pkcs7-certreqresp", "p7r"},
  {"application/x-pkcs7-mime", "p7c"},
  {"application/x-pkcs7-mime", "p7m"},
  {"application/x-pkcs7-signature", "p7s"},
  {"application/x-sh", "sh"},
  {"application/x-shar", "shar"},
  {"application/x-shockwave-flash", "swf"},
  {"application/x-stuffit", "sit"},
  {"application/x-sv4cpio", "sv4cpio"},
  {"application/x-sv4crc", "sv4crc"},
  {"application/x-tar", "tar"},
  {"application/x-tcl", "tcl"},
  {"application/x-tex", "tex"},
  {"application/x-texinfo", "texi"},
  {"application/x-texinfo", "texinfo"},
  {"application/x-troff", "roff"},
  {"application/x-troff", "t"},
  {"application/x-troff", "tr"},
  {"application/x-troff-man", "man"},
  {"application/x-troff-me", "me"},
  {"application/x-troff-ms", "ms"},
  {"application/x-ustar", "ustar"},
  {"application/x-wais-source", "src"},
  {"application/x-x509-ca-cert", "cer"},
  {"application/x-x509-ca-cert", "crt"},
  {"application/x-x509-ca-cert", "der"},
  {"application/ynd.ms-pkipko", "pko"},
  {"application/zip", "zip"},
  {"audio/basic", "au"},
  {"audio/basic", "snd"},
  {"audio/mid", "mid"},
  {"audio/mid", "rmi"},
  {"audio/mpeg", "mp3"},
  {"audio/x-aiff", "aif"},
  {"audio/x-aiff", "aifc"},
  {"audio/x-aiff", "aiff"},
  {"audio/x-mpegurl", "m3u"},
  {"audio/x-pn-realaudio", "ra"},
  {"audio/x-pn-realaudio", "ram"},
  {"audio/x-wav", "wav"},
  {"image/bmp", "bmp"},
  {"image/cis-cod", "cod"},
  {"image/gif", "gif"},
  {"image/ief", "ief"},
  {"image/jpeg", "jpe"},
  {"image/jpeg", "jpeg"},
  {"image/jpeg", "jpg"},
  {"image/pipeg", "jfif"},
  {"image/svg+xml", "svg"},
  {"image/tiff", "tif"},
  {"image/tiff", "tiff"},
  {"image/x-cmu-raster", "ras"},
  {"image/x-cmx", "cmx"},
  {"image/x-icon", "ico"},
  {"image/x-portable-anymap", "pnm"},
  {"image/x-portable-bitmap", "pbm"},
  {"image/x-portable-graymap", "pgm"},
  {"image/x-portable-pixmap", "ppm"},
  {"image/x-rgb", "rgb"},
  {"image/x-xbitmap", "xbm"},
  {"image/x-xpixmap", "xpm"},
  {"image/x-xwindowdump", "xwd"},
  {"message/rfc822", "mht"},
  {"message/rfc822", "mhtml"},
  {"message/rfc822", "nws"},
  {"text/css", "css"},
  {"text/h323", "323"},
  {"text/html", "htm"},
  {"text/html", "html"},
  {"text/html", "stm"},
  {"text/iuls", "uls"},
  {"text/plain", "bas"},
  {"text/plain", "c"},
  {"text/plain", "h"},
  {"text/plain", "txt"},
  {"text/richtext", "rtx"},
  {"text/scriptlet", "sct"},
  {"text/tab-separated-values", "tsv"},
  {"text/webviewhtml", "htt"},
  {"text/x-component", "htc"},
  {"text/x-setext", "etx"},
  {"text/x-vcard", "vcf"},
  {"video/mpeg", "mp2"},
  {"video/mpeg", "mpa"},
  {"video/mpeg", "mpe"},
  {"video/mpeg", "mpeg"},
  {"video/mpeg", "mpg"},
  {"video/mpeg", "mpv2"},
  {"video/quicktime", "mov"},
  {"video/quicktime", "qt"},
  {"video/x-la-asf", "lsf"},
  {"video/x-la-asf", "lsx"},
  {"video/x-ms-asf", "asf"},
  {"video/x-ms-asf", "asr"},
  {"video/x-ms-asf", "asx"},
  {"video/x-msvideo", "avi"},
  {"video/x-sgi-movie", "movie"},
  {"x-world/x-vrml", "flr"},
  {"x-world/x-vrml", "vrml"},
  {"x-world/x-vrml", "wrl"},
  {"x-world/x-vrml", "wrz"},
  {"x-world/x-vrml", "xaf"},
  {"x-world/x-vrml", "xof"}
 };
 
 /**
  * DBにデフォルト値をセットする
  * @param db
  */
 public static void setDefaultContentType(ContentTypeDB db){
  ContentElement element;
  
  db.startTransaction();
  for(int i=0; i< defaultTypeArray.length; i++){
   element = new ContentElement(defaultTypeArray[i][1], defaultTypeArray[i][0]);
   db.registerElement(element);
  }
  db.commit();
  db.endTransaction();
 }
}


こんな感じ。デフォルトの配列を作成するのがちょっと面倒ですがしょうがないです。 これだけできれば後はこいつを使ってインテントを投げましょう。
今回は暗黙のインテントを投げる用のクラスを作成しています。 ソースは以下。

/**
 * 暗黙のインテント呼び出し用クラス
 *
 */
public class IntentHelper {
 /**
  * @param target 受け渡すファイル
  * @param act 呼び出し元アクティビティ
  */
 static public void callIntentByFile(File target, Activity act){
  String fileExtension = getExtension(target.getName());
  ContentTypeDB db = new ContentTypeDB(act.getApplicationContext());
  String contentType = db.getType(fileExtension);
  db.close();
  if(contentType!=null){
   callIntent(act,target, getActionType(fileExtension), contentType);
  }
 }
 
 /**
  * 拡張子取得メソッド
  * @param path
  * @return
  */
 static protected String getExtension(String path){
  int pos = path.lastIndexOf(".");
  String ext = "";
  if(pos != -1){
   pos++;
   ext = path.substring(pos);
  }
  return ext;
 }
 
 /**
  * 取りあえず今はACTION_VIEW固定
  */
 static private String getActionType(String fileExtension){
  return Intent.ACTION_VIEW;
 }
 
 static private void callIntent(Activity calleeAct,File file,String action,String contentType){
  if(file==null||action==null||contentType==null){
   return;
  }
  Intent intent = new Intent();
  intent.setAction(action);
  intent.setDataAndType(Uri.fromFile(file),contentType);
  calleeAct.startActivity(intent);
 }
}



IntentHelperクラスのcallIntentByFileメソッドに受け渡すファイルと、自アクティビティを指定して 呼び出せばアラ不思議、多分候補のアプリ選択ダイアログが出てくるはずです。はずです。
後は独自に拡張子を増やしたりなんだりしてやればいいはず。そんな感じ。

10/29 追記

初期化時のデフォルト値設定でかなり時間がかかってましたが、 transactionを追加することで解消できました。 ソースのContentTypeDbクラスとContentTypeHelperクラスが変更されています。 データベース関係には今まで触れたことがあまりなかったからな。。。 ちょっとは勉強しとかないと。

2011年9月14日水曜日

イメージをグレースケールにしたりカラーに戻したり

今回はイメージボタンの画像を動的にグレースケールにしたりカラーに戻したりと いうようなことをやってみました。 使用するクラスは、ColorMatrixColorFilterクラス。
ColorMatrixクラスのインスタンスメソッドのsetSaturationで HSV色空間のS(彩度)を0に設定したマトリクスを作成します。
そのマトリクスを使ってColorMatrixColorFilterを生成することで、 グレースケール用のフィルタを作成しています。
後は、イベントをトリガーに対象のイメージボタンの ColorFilterに設定したり、nullで取り除いたりすることで、 動的にグレースケールとの切り替えが行えます。

以下適当なサンプルコード


    private ColorMatrixColorFilter grayScaleFilter;
    private ImageButton imgBtn;

    private void initialize(){
        imgBtn = (ImageButton) findViewById(id.imageButtonA);
        ColorMatrix matrix = new ColorMatrix();
        matrix.setSaturation(0);
        grayScaleFilter = new ColorMatrixColorFilter(matrix);
    }

    public void changeGrayScaleMode(){
        imgBtn.setColorFilter(grayScaleFilter);
    }
    public void changeColorMode(){
        imgBtn.setColorFilter(null);
    }


追記
厳密に言うと彩度を0にするのと、グレースケールとは違うのだけどまぁいいか。

2011年9月3日土曜日

進捗~ その2

作成してるもののメモその2です。 前回から半月しますが、なかなか進みませんね。 現在は下のような感じになってます。
左右で、接続先PCのファイルと、ローカルのファイルが表示されるようになってます。 あと、ローカルファイルの画像閲覧、ZIP/RARファイル内の閲覧とかができるようにもなってます。
音楽ファイルの再生時に表示される画面です。 ダウンロード最中でも再生されるようになっています。 時間がnullになっていますが気にしないでください。1秒後にはきちんと表示されます。
一応ファイル転送機能とかもつけてる最中ですが、GUIて難しいですね。 あとは、ファイルリスト画面の表示/非表示機能をつけて、転送中の画面つけて、 ダイアログ出したり送信状況出したり、 うーん、まだまだですね。