Pages

2013年4月27日土曜日

proxy環境下でUnityを使用する

Unityなんですが、インストール後の初の起動時に何やら認証をするために外部にアクセスしているみたいです。

proxy環境下にある場合、proxyが邪魔をしてUnityが認証できず起動に失敗してしまいます。
まぁ、そんな環境下でUnityを使う人がどれだけ存在するか知りませんが。。。

WindowsのUnityの場合、環境変数HTTP_PROXY、HTTPS_PROXYを見ているようで、
起動前にこれらの環境変数を設定する必要があります。

Unityが初期起動に失敗する場合お試し下さいませ。

2013年4月11日木曜日

iPhoneのクラッシュログの見方の補足

現在リリースしている無料アプリがあるんですが、広告システムを更新するためにライブラリの変更を行いました。
アップルの審査を無事通過し「Ready for Sale」となったので、特にDLして確認せずそのままにしておきました。

数日後、DL数の激減と評価が星一つばかりになっているのに気付き慌ててDLして確認してみた所、なんと起動しないという状態に。。。

実機ではiPhone4S、iPhone5共に動作確認していましたが、まさか事態にすぐにクラッシュログを確認することにしました。

iPhoneのクラッシュログなんですが、そのままでは何処が問題で落ちたのか分からないので、Xcodeに用意されているスクリプトを使ってゴニョゴニョやるのは知っていたんですが、いろんなブログに書いてあるやり方をしても何故か
Error: "DEVELOPER_DIR" is not defined at 
とか言われる。

いや、DEVELOPER_DIRなんか定義してないよ。ってか、どこよそれ。
って思ってたらスクリプトファイルが置いてあるちょっと上のディレクトリがDeveloperってディレクトリだったのでとりあえず↓みたいにしてみた

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer/"

もう一度実行。
$ ./symbolicatecrash xxxx.crash xxxx.app.dSYM > hoge.dat
無事出力されました。

余談ですが、クラッシュログを見ても原因が何とも言えなかったので、Appleに質問することにしました。
今まで何度か質問したことがあるのですが、私が頑張って英語メールを送ってもいつも返信が日本語だったので、今回日本語でメールを投げてみたところ、
「質問は英語でしか受付ないぜ」
的な英文メールが届きましたとさ。

2013年4月9日火曜日

ViewPagerで画面数に合わせた背景画像の移動を実現する(後編)

前回の続きで、ViewPagerでページを切り替える度に背景が移動していく箇所を実装します。

ここでの大雑把な仕様は、
・最初のページは全ページの真ん中のページ(例えば全5ページなら3ページが初期位置)
・背景画像は必ず横長の画像
・背景は横にしか移動しない
ということにします。

ちなみに私はAndroidに関しては素人なので、この実装よりもいいやり方があれば是非是非教えて頂きたい。
というか、こんな実装を必要とする人っているのか??

それでは、とりあえず実装後のMainActivityはこんな感じとなります。
public class MainActivity extends Activity {
 private CustomAdapter adapter;
 
 private ViewPager mViewPager;
 private ImageView mImageView;
 private Bitmap mBitmap;
 
 private float displayW;
 private float displayH;
 
 // -----追記
 private Matrix matrix;
 private float currentBmpX;
    private int maxMoveBmpX;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        // カスタム PagerAdapter を生成
        adapter = new CustomAdapter(this);
        adapter.add(Color.RED);
        adapter.add(Color.GREEN);
        adapter.add(Color.BLUE);
        adapter.add(Color.BLACK);
        adapter.add(Color.WHITE);
 
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        displayW = metrics.widthPixels;
        displayH = metrics.heightPixels;
        
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 画像そのものは読み込まない
        BitmapFactory.decodeResource(getResources(), R.drawable.wall, options);
        
        // dpi計算
        int bmpNativeHeight = options.outHeight;
        float dpiRatio = bmpNativeHeight / displayH;
        float calcDpi = metrics.densityDpi * dpiRatio;
        if (mBitmap == null || mBitmap.isRecycled()) {
            options.inJustDecodeBounds = false;
            options.inDensity = (int) calcDpi;
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wall, options);
        }
        
        
        mImageView = new ImageView(this);
        mImageView.setImageBitmap(mBitmap);
        mImageView.setScaleType(ScaleType.MATRIX);
        addContentView(mImageView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        
        // ViewPager を生成
        mViewPager = new ViewPager(this);
        mViewPager.setAdapter(adapter);
        
        // -----追記
        matrix = new Matrix();
        currentBmpX = (mBitmap.getScaledWidth(metrics) / 2) - (displayW / 2);
        maxMoveBmpX = (int) (mBitmap.getScaledWidth(metrics) - displayW);
        mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
         private int currentPage;
         
   @Override
   public void onPageSelected(int pageNumber) {
    currentPage = pageNumber;
    currentBmpX = getAbsPositionX(pageNumber);
   }
   
   @Override
   public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    if (positionOffsetPixels == 0) {
     return; 
    }
    
    //page移動中の処理
    float scaledOffsetPixels = positionOffsetPixels * getScaleValue();
    
    // ページ毎の始点のx座標
    float absolutePosition = getAbsPositionX(position);
    currentBmpX = absolutePosition + scaledOffsetPixels;
    
          mImageView.setImageMatrix(getMatrix(matrix, -currentBmpX));
          mImageView.invalidate();
   }
   
   @Override
   public void onPageScrollStateChanged(int state) {
   }
   
   protected float getAbsPositionX(int page) {
    return (maxMoveBmpX / (adapter.getCount() - 1)) * page;
   }
  });
        
        addContentView(mViewPager, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }
    
    @Override
    protected void onResume() {
     super.onResume();
     mViewPager.setCurrentItem((adapter.getCount()/2), true);
     mImageView.setImageMatrix(getMatrix(matrix, -currentBmpX));
    }
    
 protected Matrix getMatrix(Matrix matrix, float xPosition) {
     if (xPosition > 0) {
      xPosition = 0;
     }
     if (Math.abs(xPosition) > maxMoveBmpX) {
      xPosition = -maxMoveBmpX;
     }
      
     matrix.setTranslate(xPosition, 0);
     return matrix;
    }
    
 public float getScaleValue() {
  float displayWidth = getWindowManager().getDefaultDisplay().getWidth();
  float fullScreenSize = displayWidth * (adapter.getCount()-1); 
  return maxMoveBmpX / fullScreenSize;
 }
}

前回と比較して、onCreate()で今回追加実装した箇所には「// -----追記」とコメントしています。
上の行番号で言うと、
11〜14行目
59〜96行目
その他のメソッドは今回追加実装してます。

まぁ、これだけで読む気も失せてくるんですが、説明を(今後の自分の為にも)。
まず、61行目と62行目の
currentBmpX = (mBitmap.getScaledWidth(metrics) / 2) - (displayW / 2);
maxMoveBmpX = (int) (mBitmap.getScaledWidth(metrics) - displayW);
ですが、
currentBmpXは、画像の左上を原点としたときの、現在画面に表示されている背景画像の左上のX座標です。
これの初期位置(つまり、中央のページで表示される背景画像の左上のX座標)を計算しています。
分かりにくいけど、計算で出しているのは上の画像の赤い点の位置ですね。
次に、maxMoveBmpXですが、これは最後のページで表示される背景画像のX座標の位置となります。

まぁ、このへんは特にポイントでも何でもないです。
今回のポイントとなる実装はViewPagerのsetOnPageChangeListenerですね。
上記コードの63行目〜96行目の部分です。

このListnerはページが動いているときにその状態を通知しまくってくれるので、その情報を使って背景画像の位置をImageView.setImageMatrixでズラしていくって感じですね。

OnPageChangeListenerでのポイントといえばポイントなんですが、
ページが進むと背景画像がマイナス方向(左方向)に移動する点。
何か、感覚的に理解しにくいのは私だけ??
それ以外は特にポイントってものは無いですね。実装も大したことしてないし。

あとポイントじゃないけど74行目〜76行目のif文。
別にこれなくても普通に動くんですけど、ページ移動中にそのときの移動距離が引数で渡ってくるんですが(positionOffsetPixels)、ページ移動が完了してからも何故かしばらく0が渡ってくるので、余計な処理を省く為に入れてます。
まぁ、なんとなくです。いらない気がしますね。。。。

長々と書きましたが、これでそれなりの動きになるはずです。
備忘録ついでに誰かのお役に立てれば幸いかと。

あ、変数名やメソッド名はあまり突っ込まないでね。

2013年4月8日月曜日

ViewPagerで画面数に合わせた背景画像の移動を実現する(前編)

Androidアプリで、ホーム画面のように左右のフリックで画面が切り替わり、その画面移動に追随して背景を移動させたい。

たったコレだけの事にアホみたいに時間を費やしてしまいました。

背景をページ数で割ってそれぞれのページの背景画像として設定したらいいじゃん。って簡単な話ではなかったんですよ、コレが。

今回、困ったのが次の仕様。
・画面数が動的に増減する。
・背景に使う画像のサイズは特に決まっていない。デザインによってサイズが違う。

この条件で、最初のページから最後のページまでの移動で、ぴったり画像の端から端まで移動させたい。

イメージはこんな感じ。

画面切り替えはViewPagerを使います。
んで、背景画像はアスペクト比をそのままに画面の高さに合わせることにします。

ViewPagerの詳細な使い方はググってください。
とりあえず、背景に画像を設定し、その上に簡単なViewPagerを重ねてみます。
ただし、背景を見えるようにしたいので、ViewPagerの背景は透明としています

public class CustomAdapter extends PagerAdapter {
    private Context mContext;
    private ArrayList mList;
 
    public CustomAdapter(Context context) {
        mContext = context;
        mList = new ArrayList();
    }
 
    public void add(Integer item) {
        mList.add(item);
    }
 
    @Override
    public Object instantiateItem(View container, int position) {
        // リストから取得
        Integer item = mList.get(position);
 
        // View を生成
        TextView textView = new TextView(mContext);
        textView.setText(position + "ページ");
        textView.setTextSize(50);
        textView.setTextColor(item);
        textView.setGravity(Gravity.CENTER);
        
        // コンテナに追加
        ((ViewGroup) container).addView(textView);

        // 背景を透明にする(第一引数が0ならOK)
        container.setBackgroundColor(Color.argb(0, 0, 0, 0));
        
        return textView;
    }
 
    @Override
    public void destroyItem(View container, int position, Object object) {
        ((ViewGroup) container).removeView((View) object);
    }
 
    @Override
    public int getCount() {
        // リストのアイテム数を返す
        return mList.size();
    }
 
    @Override
    public boolean isViewFromObject(View view, Object object) {
        // Object 内に View が存在するか判定する
        return view == (TextView) object;
    }
}


public class MainActivity extends Activity {

 private ImageView imageView;
 private Bitmap img;
 
 private float displayW;
 private float displayH;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // カスタム PagerAdapter を生成
        CustomAdapter adapter = new CustomAdapter(this);
        adapter.add(Color.RED);
        adapter.add(Color.GREEN);
        adapter.add(Color.BLUE);
        adapter.add(Color.BLACK);
        adapter.add(Color.WHITE);
 
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        displayW = metrics.widthPixels;
        displayH = metrics.heightPixels;
        
        // まずは画像を読み込まずに画像情報だけを取得する
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 画像そのものは読み込まない設定
        BitmapFactory.decodeResource(getResources(), R.drawable.wall, options);
        
        // アスペクト比をそのままに、どの端末でも同じように表示させる(縦に合わせる)
        int bmpNativeHeight = options.outHeight;
        float dpiRatio = bmpNativeHeight / displayH;
        float calcDpi = metrics.densityDpi * dpiRatio;
        if (img == null || img.isRecycled()) {
            options.inJustDecodeBounds = false;
            options.inDensity = (int) calcDpi;
            img = BitmapFactory.decodeResource(getResources(), R.drawable.wall, options);
        }
        imageView = new ImageView(this);
        imageView.setImageBitmap(img);
        imageView.setScaleType(ScaleType.MATRIX);
        addContentView(imageView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        
        // ViewPager を生成
        ViewPager viewPager = new ViewPager(this);
        viewPager.setAdapter(adapter);
        addContentView(viewPager, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }

ここでのポイントは、
・ViewPagerの背景を透明にする
・背景画像のImageViewのScaleTypeをMATRIXに指定する
となります。

背景は動きませんが、とりあえず動かしたい背景画像の上にViewPagerを乗せてみました。
ちなみにAndroidアプリをほどんど開発した事が無い私は、この「背景をどの端末でも同じに見えるようにdpiを合わせる」というのにも悩みまくりました。
これをしないと端末によって背景が拡大表示されたりするので注意してください。

次はViewPagerでページを切り替える度に背景が移動していく箇所を実装していきます。


2013年4月1日月曜日

以前から、ときどきiOS関係を書いてきましたが、仕事でもAndroidを触れる機会が増えたと同時にどうしてもまとめておきたい事も増えたので、とりあえず新しくブログを書こうと思います。

基本的にスマホ関係の技術を中心に書いていこうと思ってますが、
ちょくちょく料理のことも書いていこうかと(元料理人だけに)。

あとは何かオススメのガジェットとかあれば紹介しようかなーっと。
軽いノリで考えているのですが、少しでも同じような事で悩んでる人のお役に立てれば幸いかと。