2013年2月28日木曜日

[Android] ViewPager で、追加・削除を動的にこなしてみる。

以下の動作を実現したい!

① FragmentAをFragmentStatePagerAdapterに追加
② FragmentBをFragmentStatePagerAdapterに追加
③ FragmentBをFragmentStatePagerAdapterから削除
④ FragmentCをFragmentStatePagerAdapterに追加

サンプルなんかを見ながら、FragmentStatePagerAdapterを実装してみましたが、
③までは見た目上は上手くいくが、④を実行すると③で削除したインスタンスが復元してしまいます。


いろいろ調べた結果、以下のことがわかりました。(勘違いなら指摘ください)
1. FragmentStatePagerAdapterはFragmentStatePagerAdapter#getItemで生成されたFragmentのインスタンスをメンバフィールドのListに保持している。
2. FragmentStatePagerAdapter#getItemでいくら別のインスタンスを返却しようとも、置き換わることはない
3. FragmentStatePagerAdapter#instantiateItemの実装として、メンバフィールドの値を返却する。

上記を踏まえた場合、PagerAdapterから実装しないと無理ゲー。
仕方なく PagerAdapter を実装することにします。

PagerAdapterの実装
public class SectionsPagerAdapter  extends PagerAdapter {

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * Private instance fileds
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    private final Context context;

    private final List<Section> sections;

    private final FragmentManager fragmentManager;

    private FragmentTransaction currentTransaction = null;

    private ArrayList<Fragment.SavedState> savedStatuses = new ArrayList<Fragment.SavedState>();

    private ArrayList<Fragment> fragments = new ArrayList<Fragment>();

    private Fragment currentPrimaryItem = null;

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * public constructors
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    /**
     *
     * @param context
     */
    public SectionsPagerAdapter(Context context, FragmentManager fragmentManager) {
        this.context = context;
        this.sections = new ArrayList<Section>();
        this.fragmentManager = fragmentManager;
    }

    /**
     *
     * @param context
     */
    public SectionsPagerAdapter(Context context, FragmentManager fragmentManager, List<Section> fragmentPages) {
        this.context = context;
        this.sections = fragmentPages;
        this.fragmentManager = fragmentManager;
    }

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * FragmentStatePagerAdapter methods
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    public boolean isViewFromObject(View view, Object object) {
        return (((Fragment)object).getView() == view);
    }

    /** */
    @Override
    public int getCount() {
        return this.sections.size();
    }

    /** */
    @Override
    public CharSequence getPageTitle(int position) {
        return this.sections.get(position).getTitle();
    }

    @Override
    public int getItemPosition(Object object) {
        android.util.Log.v("SectionsPageAdapter", "getItemPosition");
        int index = this.fragments.indexOf(object);
        return index != -1 ? index : PagerAdapter.POSITION_NONE;
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (this.currentTransaction != null) {
            this.currentTransaction.commitAllowingStateLoss();
            this.currentTransaction = null;
            this.fragmentManager.executePendingTransactions();
        }
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != currentPrimaryItem) {
            if (currentPrimaryItem != null) {
                currentPrimaryItem.setMenuVisibility(false);
            }

            if (fragment != null) {
                fragment.setMenuVisibility(true);
            }

            currentPrimaryItem = fragment;
        }
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        android.util.Log.v("SectionsPageAdapter", "instantiateItem:" + position);
        android.util.Log.v("SectionsPageAdapter", "this.fragments.size():" + this.fragments.size());
        if (this.currentTransaction == null) {
            this.currentTransaction = this.fragmentManager.beginTransaction();
        }

        if (this.fragments.size() > position) {
            Fragment fragment = (Fragment)this.fragments.get(position);
            if (fragment != null) {
                return fragment;
            }
        }

        Fragment fragment = this.instantiateFragment(position);
        if (this.savedStatuses.size() > position) {
            Fragment.SavedState fss = this.savedStatuses.get(position);
            if (fss != null) {
                try {
                    fragment.setInitialSavedState(fss);
                } catch (Exception ex) {
                    // Schon aktiv (kA was das heißt xD)
                    ex.printStackTrace();
                }
            }
        }

        while (this.fragments.size() <= position) {
            this.fragments.add(null);
        }

        fragment.setMenuVisibility(false);
        this.fragments.set(position, fragment);
        this.currentTransaction.add(container.getId(), fragment);

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        android.util.Log.v("SectionsPageAdapter", "destroyItem:" + position);

        Fragment fragment = (Fragment)object;

        if (this.currentTransaction == null) {
            this.currentTransaction = this.fragmentManager.beginTransaction();
        }

        //
        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         * 削除対象のFragmentがsectionsに存在するかを確認する。
         * 同一であると識別するのは、
         * ・クラス名が一致する
         * ・argumentsが一致する
         * の二点を満たした場合。
         * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
        String fullName = fragment.getClass().getName();
        Bundle arguments = fragment.getArguments();

        boolean isExist = false;
        for (Section section : this.sections) {
            // フラグメントのクラス名が一致するか
            if (section.getFragmentClassName().equals(fullName)) {
                if (section.getArguments() == null && arguments == null) {
                    // 一致
                    isExist = true;
                } else if (section.getArguments() == null){
                    // NULL で 個数が0なら一致
                    isExist = arguments.keySet().size() == 0;
                } else if (arguments == null){
                    // NULL で 個数が0なら一致
                    isExist = section.getArguments().keySet().size() == 0;
                } else {
                    boolean isKeyMatch = true;

                    // 引数のキーを確認し、一致しない項目を探します。
                    for(String key : section.getArguments().keySet()) {
                        if (!arguments.containsKey(key)) {
                            // キーが欠落
                            isKeyMatch = false;
                            break;
                        }

                        if (!section.getArguments().get(key).equals(arguments.get(key))) {
                            // 値が不一致
                            isKeyMatch = false;
                            break;
                        }
                    }

                    isExist = isKeyMatch;
                }
            }

            // 存在した為、検索を中断する。
            if (isExist) {
                break;
            }
        }


        if (isExist) {
            while (this.savedStatuses.size() <= position) {
                this.savedStatuses.add(null);
            }

            this.savedStatuses.set(position, this.fragmentManager.saveFragmentInstanceState(fragment));

            while (this.fragments.size() <= position) {
                this.fragments.add(null);
            }

            this.fragments.set(position, null);
        } else {
        }

        this.currentTransaction.remove(fragment);
    }

    @Override
    public Parcelable saveState() {
        Bundle state = null;
        if (savedStatuses.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[savedStatuses.size()];
            savedStatuses.toArray(fss);
            state.putParcelableArray("states", fss);
        }

        for (int i = 0; i < fragments.size(); i++) {
            Fragment f = fragments.get(i);
            if (f != null) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                fragmentManager.putFragment(state, key, f);
            }
        }

        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle) state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            savedStatuses.clear();
            fragments.clear();
            if (fss != null) {
                for (int i = 0; i < fss.length; i++) {
                    savedStatuses.add((Fragment.SavedState) fss[i]);
                }
            }
            Iterable<string> keys = bundle.keySet();
            for (String key : keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment fragment = fragmentManager.getFragment(bundle, key);
                    if (fragment != null) {
                        while (fragments.size() <= index) {
                            fragments.add(null);
                        }
                        fragment.setMenuVisibility(false);
                        fragments.set(index, fragment);
                    } else {
                        android.util.Log.w("SectionsPageAdapter", "Bad fragment at key " + key);
                    }
                }
            }
        }
    }

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * public methods
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    /** */
    public Fragment instantiateFragment(int position) {
        if (this.sections.size() < position) {
            return null;
        }

        return Fragment.instantiate(
                this.context,
                this.sections.get(position).getFragmentClassName(),
                this.sections.get(position).getArguments());
    }

    /**
     * Sectionを追加する。
     *
     * @param resId タイトルに使う文字列リソースのID
     * @param fragmentClass フラグメントのクラス
     * @param arguments フラグメントを初期化するためのバンドル
     */
    public void addSection(int resId, Class fragmentClass, Bundle arguments) {
        this.addSection(this.context.getString(resId), fragmentClass, arguments);
    }

    /**
     * Sectionを追加する。
     *
     * @param title タイトル
     * @param fragmentClass フラグメントのクラス
     * @param arguments フラグメントを初期化するためのBundle
     */
    public void addSection(String title, Class fragmentClass, Bundle arguments) {
        this.sections.add(new Section(title, fragmentClass, arguments));

        // 新しくタブが追加されたことを通知しておく
        this.notifyDataSetChanged();
    }

    /**
     * Sectionを削除する
     * @param section 削除するSection
     */
    public void removePage(Section section) {
        if (this.getCount() == 1) { return; }

        int position = this.sections.indexOf(section);

        this.sections.remove(section);

        while (this.savedStatuses.size() <= position) {
            this.savedStatuses.add(null);
        }

        this.savedStatuses.remove(position);

        while (this.fragments.size() <= position) {
            this.fragments.add(null);
        }

        this.fragments.remove(position);

        this.notifyDataSetChanged();
    }

    /**
     * Sectionを削除する
     * @param position 削除するインデックス
     */
    public void removePage(int position) {
        if (this.getCount() == 1) { return; }

        this.sections.remove(position);

        while (this.savedStatuses.size() <= position) {
            this.savedStatuses.add(null);
        }

        this.savedStatuses.remove(position);

        while (this.fragments.size() <= position) {
            this.fragments.add(null);
        }

        this.fragments.remove(position);

        this.notifyDataSetChanged();
    }

    /**
     * 指定したSectionを取得する
     * @param position index
     * @return
     */
    public Section getSection(int position) {
        return this.sections.get(position);
    }

    /**
     * Section情報を全て取得する。
     * @return
     */
    public ArrayList<Section> getSections() {
        int size = this.getCount();
        ArrayList>Section< result = new ArrayList>Section<(size);
        for (int index = 0; index < size; index++) {
            result.add(this.sections.get(index));
        }

        return result;
    }

    /**
     * 指定されたSectionのindexを取得する。
     * @param section indexを取得するSection
     * @return
     */
    public int getSectionPosition(Section section) {
        return this.sections.indexOf(section);
    }
}

上記で使用しているデータクラス

public class Section  implements Parcelable {

    public static final Parcelable.Creator<Section> CREATOR = (
            new Parcelable.Creator<Section>() {
                public Section createFromParcel(Parcel in) {
                    Section result = new Section(in);
                    return result;
                }

                public Section[] newArray(int size) {
                    return new Section[size];
                }
            });

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * Private instance fields
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    /**
     * 表示するタイトル
     */
    private String title;

    /**
     * フラグメントのクラス名
     */
    private String fragmentClassName;

    /**
     * フラグメントに設定する引数情報
     */
    private Bundle arguments;

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * Public Getter/Setter
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    /**
     * タイトルを取得します。
     * @return
     */
    public String getTitle() {
        return this.title;
    }

    /**
     * フラグメントに設定する引数情報を取得します。
     * @return
     */
    public Bundle getArguments() {
        return this.arguments;
    }

    /**
     * フラグメントのクラス名を取得します。
     */
    public String getFragmentClassName() {
        return this.fragmentClassName;
    }

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * Private constructors
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    private Section(Parcel in) {
        this.title = in.readString();
        this.fragmentClassName = in.readString();
        this.arguments = in.readBundle();
    }

    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
     * public constructors
     *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

    /**
     *
     * @param title
     * @param fragmentClass
     * @param arguments
     */
    public Section(String title, Class fragmentClass, Bundle arguments) {
        this.title = title;
        this.fragmentClassName = fragmentClass.getName();
        this.arguments = arguments;
    }

    /*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
     * Parcelable methods
     *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/

    /** */
    @Override
    public int describeContents() {
        return 0;
    }

    /** */
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(this.title);
        out.writeString(this.fragmentClassName);
        out.writeBundle(this.arguments);
    }
}


これで追加削除が自由自在!ひゃっはー!
もっと楽な方法がある気がします。

2012年6月20日水曜日

[Android][入門] DialogにViewを設定する際にDialog規定のテーマを適応する

タイトルだけだと何を言ってるかわからないと思います。すみません、日本語が不得手なので。

サクッと本題。

ダイアログにレイアウトファイルから生成したビューを設定するとします。

【レイアウトファイル】
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Hello !" />
</LinearLayout>

【MyActivity クラス】
public class MyActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater inflater =
                (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.sample, null);
        new AlertDialog.Builder(this)
        .setView(view)
        .create()
        .show();
    }
}

【AndroidManifest.xml】
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.goodfornothing.android.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MyActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Black">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


出力結果はこうなります。

そりゃそうだという感じ。

ではThemeを @android:style/Theme.Light に変更した場合はどうなるでしょうか。

【AndroidManifest.xml】
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.goodfornothing.android.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MyActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>



思ったダイアログと別物です。誰ですか。
文字色が暗すぎるため、なんだかとても見辛くなりました。

これはActivityのThemeの文字色に引きずられているためです。
AlertDialog#getLayoutInflater() より取得した LayoutInflater を通して View を生成すると、期待通りのDialogが表示されます。


【MyActivity クラス】
public class MyActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        AlertDialog dialog = new AlertDialog.Builder(this).create();

        LayoutInflater inflater = dialog.getLayoutInflater();
        View view = inflater.inflate(R.layout.sample, null);

        dialog.setView(view);
        dialog.show();
    }
}



期待通りですね。

2012年6月13日水曜日

DPI値を取得する方法

// DPI値を取得する
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, this.getResources().getDisplayMetrics())

2012年6月11日月曜日

AndroidでSilverlightのWrapPanel風のViewGroupが欲しい その2

前回作ったコントロールでは、動的にadd もしくは remove した時に正常に動作しませんでした。


今回は ViewGroup を直接継承し実装してみました。
子要素の配置を行うためいに ViewGroup#onLayout をオーバーライドします。
ここで View#getWidth の戻り値は 0 となってしまうため、 View#getMeasuredWidthを使用します。
ですが、素の状態では View#getMeasuredWidth の戻り値も 0 となり、コントロールのサイズを取得できません。
そこでまず ViewGroup#onMeasure をオーバーライドし子要素に対してView#measureを呼び出します。


以下の呪文を記述
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = View.resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = View.resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        this.setMeasuredDimension(width, height);

        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
        int childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED);

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).measure(childWidthSpec, childHeightSpec);
        }
    }

これを書けば、View#getMeasuredWidthで値が取得できるようになります。
ちゃんと理解してはいない。

あとは前回 View#onWindowFocusChangedのなかに書いてた事をView#onLayoutに記述してやればOK。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int count = this.getChildCount();

        // 行の高さ
        int rowHeight = 0;

        // 現在の行の幅
        int currentRowWidth = 0;

        int rowMaxWidth = this.getWidth();

        int top = 0;

        for (int i = 0; i < count; i++) {
            View child = this.getChildAt(i);
            if (child.getVisibility() != View.GONE) {

                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();

                if (rowMaxWidth < currentRowWidth + childWidth) {
                    // はみ出すので演算をやり直す。
                    // 次行の位置を設定
                    top += rowHeight < childHeight ? childHeight : rowHeight;
                    // 現在行の高さを初期化
                    rowHeight = 0;
                    // 現在行の幅を初期化
                    currentRowWidth = 0;
                }

                // コントロールを適切な位置に配置
                child.layout(
                    currentRowWidth,
                    top,
                    currentRowWidth + childWidth,
                    top + childHeight);

                // 行の高さを更新する必要があるか
                rowHeight =
                    rowHeight < childHeight ? childHeight : rowHeight;

                // 現在のコントロール幅を合計値に加算する
                currentRowWidth += childWidth;
            }
        }
    }

良い感じ良い感じ。

実際にはMarginなんかが設定されるハズ。
なの以下の クラス をInner Classとして作成。
    public static class LayoutParams extends MarginLayoutParams {

        public LayoutParams(Context context, AttributeSet attr) { super(context, attr); }

        public LayoutParams(int width, int height) { super(width, height); }

        public LayoutParams(ViewGroup.LayoutParams source) { super(source); }

        public LayoutParams(MarginLayoutParams source) { super(source); }
    }

MarginLayoutParams を継承しただけ。今後を考えてInner Classとした。
このクラスを定義しただけで、(WrapLayout.LayoutParams)child.getLayoutParams() なんてした日には楽しい結末(ClassCastException)が待っているので、 以下の Method を Override
・ ViewGroup#generateLayoutParams
・ ViewGroup#generateDefaultLayoutParams
・ ViewGroup#checkLayoutParams
・ ViewGroup#generateLayoutParams
    @Override
    public WrapLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new WrapLayout.LayoutParams(getContext(), attrs);
    }

    @Override
    protected WrapLayout.LayoutParams generateDefaultLayoutParams() {
        return new WrapLayout.LayoutParams(WrapLayout.LayoutParams.WRAP_CONTENT, WrapLayout.LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof WrapLayout.LayoutParams;
    }

    @Override
    protected WrapLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new WrapLayout.LayoutParams(p);
    }

設定された margin を使って配置を行うように ViewGroup#onLayout に修正を加える。
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int count = this.getChildCount();

        // 行の高さ
        int rowHeight = 0;

        // 現在の行の幅
        int currentRowWidth = 0;

        int rowMaxWidth = this.getWidth();

        int top = 0;

        for (int i = 0; i < count; i++) {
            View child = this.getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                WrapLayout.LayoutParams lp =
                        (WrapLayout.LayoutParams)child.getLayoutParams();

                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();

                int childTotaWidth = childWidth + lp.rightMargin + lp.leftMargin;
                int childTotalHeight = childHeight + lp.topMargin + lp.bottomMargin;

                if (rowMaxWidth < currentRowWidth + childTotaWidth) {
                    // はみ出すので演算をやり直す。
                    // 高さを設定
                    top += rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
                    // 現在行の高さを初期化
                    rowHeight = 0;
                    // 現在行の幅を初期化
                    currentRowWidth = 0;
                }

                child.layout(
                    currentRowWidth + lp.leftMargin,
                    top + lp.topMargin,
                    currentRowWidth + childWidth + lp.rightMargin,
                    top + childHeight + lp.bottomMargin);

                rowHeight =
                    rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
                currentRowWidth += childTotaWidth;
            }
        }
    }

これでMarginが有効になったWrapLayoutの出来上がり。

ソースコード全文(import等は割愛)

public class WrapLayout extends ViewGroup {

    //==================================================================
    // constructor
    //==================================================================
    public WrapLayout(Context context) { super(context); }

    public WrapLayout(Context context, AttributeSet attrs) { super(context, attrs); }

    public WrapLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }

    //==================================================================
    // public methods
    //==================================================================

    @Override
    public WrapLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new WrapLayout.LayoutParams(getContext(), attrs);
    }

    //==================================================================
    // protected methods
    //==================================================================
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = View.resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = View.resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        this.setMeasuredDimension(width, height);

        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
        int childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED);

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).measure(childWidthSpec, childHeightSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int count = this.getChildCount();

        // 行の高さ
        int rowHeight = 0;

        // 現在の行の幅
        int currentRowWidth = 0;

        int rowMaxWidth = this.getWidth();

        int top = 0;

        for (int i = 0; i < count; i++) {
            View child = this.getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                WrapLayout.LayoutParams lp =
                        (WrapLayout.LayoutParams)child.getLayoutParams();

                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();

                int childTotaWidth = childWidth + lp.rightMargin + lp.leftMargin;
                int childTotalHeight = childHeight + lp.topMargin + lp.bottomMargin;

                if (rowMaxWidth < currentRowWidth + childTotaWidth) {
                    // はみ出すので演算をやり直す。
                    // 高さを設定
                    top += rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
                    // 現在行の高さを初期化
                    rowHeight = 0;
                    // 現在行の幅を初期化
                    currentRowWidth = 0;
                }

                child.layout(
                    currentRowWidth + lp.leftMargin,
                    top + lp.topMargin,
                    currentRowWidth + childWidth + lp.rightMargin,
                    top + childHeight + lp.bottomMargin);

                rowHeight =
                    rowHeight < childTotalHeight ? childTotalHeight : rowHeight;
                currentRowWidth += childTotaWidth;
            }
        }
    }

    @Override
    protected WrapLayout.LayoutParams generateDefaultLayoutParams() {
        return new WrapLayout.LayoutParams(WrapLayout.LayoutParams.WRAP_CONTENT, WrapLayout.LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof WrapLayout.LayoutParams;
    }

    @Override
    protected WrapLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new WrapLayout.LayoutParams(p);
    }

    //==================================================================
    // inner class
    //==================================================================

    public static class LayoutParams extends MarginLayoutParams {

        public LayoutParams(Context context, AttributeSet attr) { super(context, attr); }

        public LayoutParams(int width, int height) { super(width, height); }

        public LayoutParams(ViewGroup.LayoutParams source) { super(source); }

        public LayoutParams(MarginLayoutParams source) { super(source); }
    }
}

2012年6月6日水曜日

AndroidでSilverlightのWrapPanel風のViewGroupが欲しい

AndroidでSilverlightのWrapPanelのように自動で折り返すViewGroupが欲しいです。
デフォルトじゃないみたいなので、いろいろ漁ってると以下の記事を発見。

・Androidでボタンを横に並べて自動で折り返す

ほほー。RelativeLayoutでござるか。

使 っ た こ と な い ん で す よ ね 。

上記の記事を使いまわしが効くようにコントロール化してみました。
かなり適当。
public class WrapLayout extends RelativeLayout {

    public WrapLayout(Context context) {
        super(context);
    }

    public WrapLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WrapLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void addView(View child) {
        // idが未設定の場合は乱数でどうにかする。
        if (child.getId() == -1) { child.setId(new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE)); }

        super.addView(child);
    }

    @Override
    public void addView(View child, int width, int height) {
        // idが未設定の場合は乱数でどうにかする。
        if (child.getId() == -1) { child.setId(new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE)); }

        super.addView(child, width, height);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        // idが未設定の場合は乱数でどうにかする。
        if (child.getId() == -1) { child.setId(new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE)); }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index) {
        // idが未設定の場合は乱数でどうにかする。
        if (child.getId() == -1) { child.setId(new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE)); }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        // idが未設定の場合は乱数でどうにかする。
        if (child.getId() == -1) { child.setId(new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE)); }

        super.addView(child, index, params);
      }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {

        super.onWindowFocusChanged(hasWindowFocus);

        // 子供の数を取得
        int l = this.getChildCount();
        // 無いなら何もしない
        if (l == 0) {
          return;
        }

        // ディスプレイの横幅を取得
        WindowManager wm = (WindowManager) this.getContext().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();

        // 1行最大長
        int max = display.getWidth();

        // 一番最初は基点となるので何もしない
        View pline = this.getChildAt(0);
        // 現在の1行の長さ
        int currentTotal = pline.getWidth();
        for (int i = 1; i < l; i++) {
            int w = this.getChildAt(i).getWidth();
            RelativeLayout.LayoutParams layoutParams =
                    (RelativeLayout.LayoutParams) this.getChildAt(i).getLayoutParams();

            // 横幅を超えないなら前のボタンの右に出す
            if (max > currentTotal + w) {
                // 現在長に処理対象コントロール長を加算
                currentTotal += w;
                layoutParams.addRule(RelativeLayout.ALIGN_TOP, this.getChildAt(i - 1).getId());
                layoutParams.addRule(RelativeLayout.RIGHT_OF, this.getChildAt(i - 1).getId());
            } else {
                // 横最大長を超過した場合は折り返す
                layoutParams.addRule(RelativeLayout.BELOW, pline.getId());

                // 基点を変更
                pline = this.getChildAt(i);

                // 長さをリセット
                currentTotal = pline.getWidth();
            }
        }
    }
}


わかってます。onWindowFocusChangedにaddViewしたらダメじゃね?ってのはわかってます。
とりあえずです。
[[Activity]]
public class MainActivity extends Activity {
    /** Called when the activity is first created. */

    String[] strings = { "てst1", "てst2", "てst3", "てst4", "てst5", "てst6", "てst7", "てst8", "てst9", "てst10", "てst111111a", "てst111", "てst234"};
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        WrapLayout layout = (WrapLayout)this.findViewById(R.id.FixedGridLayout);
        for (String string : strings) {
            Button btn = new Button(this);
            btn.setText(string);
            //btn.setId(random.nextInt(Integer.MAX_VALUE));
            layout.addView(btn);
        }
    }
}
[[出力結果]]

良い感じ。

2012年6月5日火曜日

[Silverlight] UIスレッド上で処理を実行する。

System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>{ .... });

Triggerでシーケンス値を自動採番


以下のようなテーブルが存在したとする。
テーブル名 : [MST_Magazine]
フィールド

項目名型備考
ID数値PK 連番でデータを格納
Publisher_ID数値
Name文字列



このようなテーブルではIDはシーケンスでの採番となる。
しかし、OracleではSQLServerのように、型として自動採番の型が存在しない。

そこで、トリガを用いて自動採番を行う。

CREATE OR REPLACE TRIGGER TRG_MST_Magazine_ID
BEFORE Insert
ON MST_Magazine
FOR EACH ROW
BEGIN
    IF :NEW.ID  IS NULL THEN
        SELECT SEQ_Magazine_ID.NEXTVAL INTO :NEW.ID FROM DUAL;
    END IF;
END;

上記のトリガは、MST_MagazineテーブルへのInsert時に起動し、
InsertされたMST_Magazine.ID項目がNULLの場合、SEQ_Magazine_ID.Nextvalの値を使用する。

これにより、IDを指定せずMST_MagazineテーブルへのInsertを行った場合は自動で数値が採番される。

HtmlPage.Window.Navigate で cookie を使用する。




Silverlightなんかで、画面を切り替えるときに HtmlPage.Window.Navigate で遷移させたりしますけども、
その時にCookieを使用したいと思いました。

System.Net.Cookieとかあるからこれを使うのかと思ったけども、どうやら違う。
こいつはHttpWebRequestの時に使うみたい。
それじゃあ、こまります。
ええ、困りますとも。
だって画面遷移をさせたいんだから。

さてどうするかと言うと、System.Windows.Browser.HtmlPageのDocumentプロパティのSetPropertyで設定してあげればいいんでがす。

[[サンプル]]
HtmlPage.Document.SetProperty( "cookie", "Key1=Value1" );
        HtmlPage.Document.SetProperty( "cookie", "Key2=Value2" );
        HtmlPage.Window.Navigate
        (
            new Uri( "http://....", UriKind.RelativeOrAbsolute),
            "_self"
        );



で、Cookie情報を取得するには HtmlPage.Document.Cookies プロパティに格納される。
これは文字列でドバーっと入ってるので、いいように加工して使うしか無いんでしょうか。
めんどくさい。

LINQで配列


// AからZまでのchar型の配列を生成 var az = new (List<char>)(System.Linq.Enumerable.Range('A', 'Z' - 'A' + 1).Select(i => (char)i).ToArray());

2010年6月10日木曜日

Windows7にOracle10g R2 を入れてみた


以前Windows7にOracle10gR1を入れようとして発狂したエントリ を書きましたが、
R2ならいけるよーとのコメントをいただいたので、今更やってみました。


とりあえずそのままインストールしようとして、
Versionが対応外なんですけ?的なエラー
じゃあ、Varsionをごまかせ☆と、setup.exeのプロパティから互換性タブの値をチョロッと編集。
ここはR1での作業と一緒。
さてさてどうなることか。



あれ?なんかインストールできましたけど?
あれ?DBにも接続できましたけど?

わおwwwwwwwwww

あの苦労は一体・・・
oracle10g用のSQLをSQLServer2005用にコンバージョンした苦労って・・・

とりあえず、select * from PRODUCT_COMPONENT_VERSIONして取得した結果

Oracle Database 10g Enterprise Edition
10.2.0.3.0
Prod

うん入ってるね。
これで幸せ。

2009年12月3日木曜日

Windows7 + oracle 10g R1 試行手順

!!! これは10g R1に関するエントリです。10g R2はこちら !!!


アクセス解析見てると、「oracle10g」を「Windows7」にぶち込みたい方が結構いるみたいですね。
俺が試したインストール手順をとりあえず書いときます。
まぁ失敗したんだけどねwwwwwww
書いてたら、詳しい人が解決策を教えてくれるかもという、淡い期待です。


「setup.exe」をそのまま起動すると、
「お前のWindowsのVerは対応してないんですけどwwww」
っていってきます。

じゃ、誤魔化しちゃいましょう。

「setup.exe」のプロパティを開きます。
「互換性」ってタブが存在するかと思います。
「互換モード」の「互換モードでこのプログラムを実行する」にチェックつけてやってください。

とりあえずこれでOk

あとは、「setup.exe」を起動するとちゃんとインストーラが動くはず。

こっから先のインストール手順に関しては他のサイトで、
山のように解説してくださってるんで割愛します。

で、問題が発生するのは、一番最後。
「Oracle Database Configuration Assistant」とやらのインストール時です。
これが「アクセス権ナスwwwwwww涙目wwwwww」(12546[アクセス権なし])ってエラーをはきます。

ここで積む。

続行すると、
「「Oracle Database Configuration Assistant」のインストールに失敗したけど、必須じゃないからよくね?」
的な文章がむにゅっとポップアップで出てきます。

あれ?そうなの?びっくりさせないでよねwwwww
とか思って、とりあえずSQL*Plus起動すっかなーと、
コマンドプロンプトで[sqlplus]って打ち込むと、
「アクセス権ナスwwwwwww涙目wwwwww」(12546[アクセス権なし])ってエラーをはきます。

だめじゃねーかwwwwwwwwwwwww俺涙目wwwwwwwwwwww
ってなるかと思います。

エラーコードは同一なことから見ても、おそらく原因は同じと思われます。
アクセス権がどのアクセス権なのかもサッパリです。
詳しい人ボスケテ

2009年11月7日土曜日

任意形式のXMLリクエスト・レスポンスの送受信


0. 準備① すでにEclipse、WTP、Axis2の設定は完了していること。
「Eclipse + Axis2連携手順 及び簡易サービス作成手順 」参照
1. サーバ側プロジェクトの作成① ファイル -> 新規 -> プロジェクト -> Web -> 動的Webプロジェクト を選択する
② 構成の変更からプロジェクト・フィアセットを開き、以下の項目のみを有効にする
Axis2 Webアプリケーション
Java
動的Webモジュール
③ 任意のプロジェクト名を入力
④ 完了を押下
2. サーバ側プロジェクト初期設定① Eclipseのワークスペース \ 作成したプロジェクト\ WebContent \ WEB-INF \ lib ディレクトリに
Axis2を展開したディレクトリ\lib ディレクトリ配下のjarファイルをコピーする
② プロジェクトをリフレッシュ
③ Web.xmlを書き換える
org.apache.axis2.transport.http.AxisAdminServlet
↓
org.apache.axis2.webapp.AxisAdminServlet
3. サービスの作成① 以下のクラスを作成する
public class test {
    public OMElement echo( OMElement request ){ return request; }
}

echoメソッドは送信されてきたメッセージを、そのまま返却するものである
4. Webサービスの登録① 作成したクラスを右クリック -> Webサービス -> Webサービスの作成を実行
② デフォルトの設定で十分なので、なにも設定せずそのまま完了ボタンを押下
この作業でWSDLが作成される。
5. クライアントプロジェクトの作成① ファイル -> 新規 -> プロジェクト -> JAVA -> JAVAプロジェクト を選択
② 任意のプロジェクト名を入力
③ 完了を押下
6. クライアントプロジェクト初期設定① 作成したプロジェクトのプロパティを開く
② JAVAのビルド・パスを表示
③ 外部JARの追加を押下
④ Axis2を展開したディレクトリ \ lib 配下のJarをすべて追加
7. クライアントを作成① 以下のクラスを作成する
# 適宜編集してください
public class Client {
    static public void main (String[] args ) {
        {
        // エンドポイントURL
        // ~/services/以降がクラス名
        String endpointStr = "http://localhost:8080/サーバ側プロジェクト名/services/test/";
        ServiceClient sender = null;
        Options options = new Options();
        try {
            sender = new ServiceClient
        } catch(AxisFault e ){
            e.printStackTrace();
           return;
        }

        // オプションの設定
        // エンドポイントの設定
        options.setTo(new EndpointReference( endpointStr ) );

        // プロトコルの設定
        options.setTransportInProtocol( Constants.TRANSPORT_HTTP);

        // オプションを反映する
        sender.setOptions( options );//送信メッセージの作成
        OMFactory fac = OMAbstractFactory.getOMFactory( );

        // ネームスペースの生成
        OMNamespace omNs = fac.createOMNamespace("http://作成したサービスのパッケージ" , "Request" );

        // 要素の作成
        OMElement request =
            fac.createOMElement(
                "echo", // 実行するメソッド
                omNs);
        OMElement msg = fac.createOMElement("Msg", omNs);
        // テキストの追加
        msg.setText ( "Hello, World !!!" );

        // 要素の追加
        request.addChild( msg );
        try{
            // リクエストの送信、レスポンスの受信
            OMElement response = sender.sendReceive( request );

            // レスポンスをコンソールに表示
            System.out.println( "レスポンス : " + response.toString() );
        }catch(AxisFault e ){
            e.printStackTrace();
           return;
        }
    }
}
8. 通信の確認① サーバを起動する。
② クライアントを実行する。
③ コンソール上に以下の表示があれば完了です
# 以下の文は適宜改行しています。
本来は一行で表示されます。
レスポンス :
<echoresponse xmlns:ns="http://サービスパッケージ名">
<return>
<msg xmlns:request="http://サービスパッケージ名">
Hello, World !!!
</msg>
</return>
</echoresponse>

2009年11月6日金曜日

Apache Axis2 + Eclipse + WTP 導入手順

0. 準備

・ Eclipse 3.5
・ WTP
( http://mergedoc.sourceforge.jp/pleiades-redirect/3.4/pleiades_java.zip.html )
・ Axis2 1.5 Standard Binary Distribution
( http://ftp.kddilabs.jp/infosystems/apache/ws/axis2/1_5/axis2-1.5-bin.zip )
・ Tomcat6.0
( http://ftp.riken.jp/net/apache/tomcat/tomcat-6/v6.0.20/bin/apache-tomcat-6.0.20.exe )

上記ファイルをダウンロードする。

1. Eclipse 設定

① ウィンドウ -> 設定 -> Webサービス -> Axis2設定
[ Axis2 ランタイム ]タブの[ Axis2 ランタイム・ロケーション ]に
Axis2を展開したディレクトリを設定する。
② ウィンドウ -> 設定 -> Webサービス -> サーバとランタイム
[ Web サーバ・ランタイム ] を [ Apache Axis2 ]に設定する

2. プロジェクトの作成

① ファイル -> 新規 -> プロジェクト -> Web -> 動的Webプロジェクト を選択する
② 構成の変更からプロジェクト・フィアセットを開き、以下の項目のみを有効にする
Axis2 Webアプリケーション
Java
動的Webモジュール
③ 任意のプロジェクト名を入力
④ 完了を押下

3. プロジェクト初期設定

① Eclipseのワークスペース \ 作成したプロジェクト\ WebContent \ WEB-INF \ lib ディレクトリに
Axis2を展開したディレクトリ\lib ディレクトリ配下のjarファイルをコピーする。
② プロジェクトをリフレッシュ

③ Web.xmlを書き換える
org.apache.axis2.transport.http.AxisAdminServlet
↓
org.apache.axis2.webapp.AxisAdminServlet

4. サービスの作成

① 以下のクラスを作成する。

public class test{
        public String Hello () {
            return "Hello!";
        }
        public String Hello2 ( String name ) {
            return "Hello, " + name + "!";
        }
        public String[] Hellos () {
            String[]    helloArray  =   {   "Hello!"    ,
                                            "Hello!!"   ,
                                            "Hello!!!"
                                            };
            return    helloArray;
        }
    }


5. Webサービスの登録

① 作成したクラスを右クリック -> Webサービス -> Webサービスの作成を実行
② デフォルトの設定で十分なので、なにも設定せずそのまま完了ボタンを押下。

6. 通信の確認

① 以下のURLに接続する。
・http://localhost:8080/ プロジェクト名/services/test/Hello
[[期待応答]]
<ns:HelloResponse xmlns:ns="http://ws.apache.org/axis2">
        <ns:return>Hello!</ns:return>
    </ns:HelloResponse>
・http://localhost:8080/ プロジェクト名/services/test/Hello2?name=Bob
[[期待応答]]
<ns:Hello2Response xmlns:ns="http://ws.apache.org/axis2">
        <ns:return>Hello, Bob!</ns:return>
    </ns:Hello2Response>
・http://localhost:8080/ プロジェクト名/services/test/Hellos
[[期待応答]]
<ns:HellosResponse xmlns:ns="http://ws.apache.org/axis2">
        <ns:return>Hello!</ns:return>
        <ns:return>Hello!!</ns:return>
        <ns:return>Hello!!!</ns:return>
    </ns:HellosResponse>
*期待応答はすべてRESTの応答である