【AndroidでSkobblerを使う】SKCoordinateRegionを使った地図の移動制限

このエントリーを Google ブックマーク に追加
Pocket
[`yahoo` not found]

地図を表示している画面はSKCoordinateRegionという領域に分割されている。
現在表示しているSKCoordinateRegionが何らかの理由(移動、拡大縮小等)で「変化開始」・「変化」・「変化終了」のときに、SKMapSurfaceListenerの対応するメソッドが呼ばれる。
今回はこの検知イベントを利用して、地図に表示制限をかける。

    private SKCoordinate tokyo = new SKCoordinate(139.766084, 35.681382);
    private SKBoundingBox limitSkBoundingBox = new SKBoundingBox(
        tokyo.getLatitude() + 0.1,
        tokyo.getLongitude() - 0.1,
        tokyo.getLatitude() - 0.1,
        tokyo.getLongitude() + 0.1
    );
    private SKCoordinateRegion lastValidRegion;
    // checks if a given region is inside the bounding box
    private boolean isInBoundingBox(SKCoordinateRegion newRegion) {
        //SKCoordinateRegionを現在表示している左上と右下端緯度経度を持つSKBoundingBoxに変換する。
        SKBoundingBox newBoundingBox = mapView.getBoundingBoxForRegion(newRegion);
        if (newBoundingBox.getTopLeftLatitude() > limitSkBoundingBox.getTopLeftLatitude()
            || newBoundingBox.getBottomRightLatitude() < limitSkBoundingBox.getBottomRightLatitude()
            || newBoundingBox.getTopLeftLongitude() < limitSkBoundingBox.getTopLeftLongitude()
            || newBoundingBox.getBottomRightLongitude() > limitSkBoundingBox.getBottomRightLongitude()) {
                return false;
        }
        return true;
    }

    @Override
    public void onMapRegionChangeStarted(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化開始で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChangeStarted");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
    }

    @Override
    public void onMapRegionChanged(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChanged");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
        if (isInBoundingBox(skCoordinateRegion)) {
            if (lastValidRegion == null) {
                lastValidRegion = new SKCoordinateRegion(skCoordinateRegion.getCenter(), skCoordinateRegion.getZoomLevel());
            } else {
                lastValidRegion.setCenter(skCoordinateRegion.getCenter());
                lastValidRegion.setZoomLevel(skCoordinateRegion.getZoomLevel());
            }
        } else {
            if (lastValidRegion != null) {
                //指定したRegionに移動する。
                mapView.changeMapVisibleRegion(lastValidRegion, false);
            }
        }
    }

    @Override
    public void onMapRegionChangeEnded(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化終了で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChangeEnded");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
    }
SKCoordinateRegionは「画面中央の緯度経度」と「ズーム率」を保持するクラス、SKMapSurfaceView#getBoundingBoxForRegionを使うことで地図の「左上の緯度経度」と「右下の緯度経度」を持つSKBoundingBoxに変換することができる。
public class MainActivity extends AppCompatActivity implements SKPrepareMapTextureListener, SKMapSurfaceListener {
    private SKMapSurfaceView mapView;
    private SKMapViewHolder mapHolder;
    String mapResourcesDirPath;
    private SKCoordinate tokyo = new SKCoordinate(139.766084, 35.681382);
    private SKBoundingBox limitSkBoundingBox = new SKBoundingBox(
        tokyo.getLatitude() + 0.1,
        tokyo.getLongitude() - 0.1,
        tokyo.getLatitude() - 0.1,
        tokyo.getLongitude() + 0.1
    );

    private SKCoordinateRegion lastValidRegion;
    // checks if a given region is inside the bounding box
    private boolean isInBoundingBox(SKCoordinateRegion newRegion) {
        //SKCoordinateRegionを現在表示している左上と右下端緯度経度を持つSKBoundingBoxに変換する。
        SKBoundingBox newBoundingBox = mapView.getBoundingBoxForRegion(newRegion);
        if (newBoundingBox.getTopLeftLatitude() > limitSkBoundingBox.getTopLeftLatitude()
            || newBoundingBox.getBottomRightLatitude() < limitSkBoundingBox.getBottomRightLatitude()
            || newBoundingBox.getTopLeftLongitude() < limitSkBoundingBox.getTopLeftLongitude()
            || newBoundingBox.getBottomRightLongitude() > limitSkBoundingBox.getBottomRightLongitude()) {
                return false;
        }
        return true;
    }

    @Override
    public void onMapRegionChangeStarted(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化開始で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChangeStarted");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
    }

    @Override
    public void onMapRegionChanged(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChanged");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
        if (isInBoundingBox(skCoordinateRegion)) {
            if (lastValidRegion == null) {
                lastValidRegion = new SKCoordinateRegion(skCoordinateRegion.getCenter(), skCoordinateRegion.getZoomLevel());
            } else {
                lastValidRegion.setCenter(skCoordinateRegion.getCenter());
                lastValidRegion.setZoomLevel(skCoordinateRegion.getZoomLevel());
            }
        } else {
            if (lastValidRegion != null) {
                //指定したRegionに移動する。
                mapView.changeMapVisibleRegion(lastValidRegion, false);
            }
        }
    }

    @Override
    public void onMapRegionChangeEnded(SKCoordinateRegion skCoordinateRegion) {
        //SKCoordinateRegionの変化終了で呼ばれる
        Log.d("SKMapSurfaceListener", "onMapRegionChangeEnded");
        Log.d("SKMapSurfaceListener", "SKBoundingBox:[Latitude-" + skCoordinateRegion.getCenter().getLatitude() + "/Longitude-" + skCoordinateRegion.getCenter().getLongitude() + "]");
    }

    @Override
    public void onInternetConnectionNeeded() {
    }

    @Override
    public void onPOIClusterSelected(SKPOICluster skpoiCluster) {
    }

    @Override
    public void onMapPOISelected(SKMapPOI skMapPOI) {
    }

    @Override
    public void onCustomPOISelected(SKMapCustomPOI skMapCustomPOI) {
    }

    @Override
    public void onCompassSelected() {
    }

    @Override
    public void onCurrentPositionSelected() {
    }

    @Override
    public void onObjectSelected(int i) {
    }

    @Override
    public void onInternationalisationCalled(int i) {
    }

    @Override
    public void onBoundingBoxImageRendered(int i) {
    }

    @Override
    public void onGLInitializationError(String s) {
    }

    @Override
    public void onScreenshotReady(Bitmap bitmap) {
    }

    @Override
    public void onAnnotationSelected(SKAnnotation skAnnotation) {
        Log.d("SKMapSurfaceListener", "onAnnotationSelected");
        Log.d("SKMapSurfaceListener", "onAnnotationSelected[x:" + skAnnotation.getUniqueID() + "]");
        //changeAnnotations(skAnnotation);
    }

    @Override
    public void onSingleTap(SKScreenPoint skScreenPoint) {
        //タップ(ActionDown-ActionUp)したときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onSingleTap");
        Log.d("SKMapSurfaceListener", "onSingleTap[x:" + skScreenPoint.getX() + "/y:" + skScreenPoint.getY() + "]");
        SKAnnotation skAnnotation = createSKAnnotation(
            mapView.getAllAnnotations().size(),
            SKAnnotation.SK_ANNOTATION_TYPE_MARKER,
            0,
            skScreenPoint, null);
        SKAnimationSettings skAnimationSettings = new SKAnimationSettings(
            SKAnimationSettings.SKAnimationType.PULSE_CCP,
            SKAnimationSettings.SKEasingType.EASE_IN_EXPO,
            1000
        );
        mapView.addAnnotation(skAnnotation, skAnimationSettings);
    }

    @Override
    public void onDoubleTap(SKScreenPoint skScreenPoint) {
        //ダブルタップ(ActionDown-ActionUp-ActionDown-ActionUp)したときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onDoubleTap");
        Log.d("SKMapSurfaceListener", "onDoubleTap[x:" + skScreenPoint.getX() + "/y:" + skScreenPoint.getY() + "]");
        ImageView imageView = new ImageView(getApplicationContext());
        imageView.setImageResource(R.mipmap.ic_launcher);
        SKAnnotation skAnnotation = createSKAnnotation(
            mapView.getAllAnnotations().size(),
            SKAnnotation.SK_ANNOTATION_TYPE_MARKER,
            0,
            skScreenPoint,
            imageView
        );
        SKAnimationSettings skAnimationSettings = new SKAnimationSettings(
            SKAnimationSettings.SKAnimationType.PIN_DROP,
            SKAnimationSettings.SKEasingType.EASE_IN_EXPO,
            1000
        );
        mapView.addAnnotation(skAnnotation, skAnimationSettings);
    }

    @Override
    public void onSurfaceCreated(SKMapViewHolder skMapViewHolder) {
        mapHolder = skMapViewHolder;
        mapView = skMapViewHolder.getMapSurfaceView();
        mapView.centerMapOnPosition(new SKCoordinate(139.766084, 35.681382));
        // initMapSettings();
        // settingScaleView();
    }

    @Override
    public void onLongPress(SKScreenPoint skScreenPoint) {
        //長押ししたときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onLongPress");
        Log.d("SKMapSurfaceListener", "onLongPress[x:" + skScreenPoint.getX() + "/y:" + skScreenPoint.getY() + "]");
    }

    @Override
    public void onActionPan() {
        //移動したときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onActionPan");
    }

    @Override
    public void onActionZoom() {
        //ズームしたときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onActionZoom");
    }

    @Override
    public void onRotateMap() {
        //回転したときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onRotateMap");
    }

    @Override
    public void onMapActionDown(SKScreenPoint skScreenPoint) {
        //マップに指が触れたときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onMapActionDown");
        Log.d("SKMapSurfaceListener", "onMapActionDown[x:" + skScreenPoint.getX() + "/y:" + skScreenPoint.getY() + "]");
    }

    @Override
    public void onMapActionUp(SKScreenPoint skScreenPoint) {
        //マップから指から離れたときに呼ばれる。
        Log.d("SKMapSurfaceListener", "onMapActionUp");
        Log.d("SKMapSurfaceListener", "onMapActionUp[x:" + skScreenPoint.getX() + "/y:" + skScreenPoint.getY() + "]");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mapResourcesDirPath = getFilesDir().getPath() + "/" + "SKMaps/";
        if (!new File(mapResourcesDirPath).exists()) {
            new SKPrepareMapTextureThread(this, mapResourcesDirPath, "SKMaps.zip", this).start();
        } else {
            initMapSetting();
        }
    }

    @Override
    public void onMapTexturesPrepared(boolean b) {
        if (b) {
            initMapSetting();
        }
    }

    private void initMapSetting() {
        SKMapsInitSettings initMapSettings = new SKMapsInitSettings();
        initMapSettings.setMapResourcesPaths(mapResourcesDirPath,
        new SKMapViewStyle(mapResourcesDirPath + "daystyle/", "daystyle.json"));
        final SKAdvisorSettings advisorSettings = initMapSettings.getAdvisorSettings();
        advisorSettings.setAdvisorConfigPath(mapResourcesDirPath + "Advisor");
        advisorSettings.setResourcePath(mapResourcesDirPath + "Advisor/Languages");
        advisorSettings.setLanguage(SKAdvisorSettings.SKAdvisorLanguage.LANGUAGE_EN);
        advisorSettings.setAdvisorVoice("en");
        initMapSettings.setAdvisorSettings(advisorSettings);
        SKMaps.getInstance().initializeSKMaps(this, initMapSettings);
        SKMapFragment mapFragment = new SKMapFragment();
        mapFragment.setMapSurfaceListener(this);
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.container, mapFragment).commit();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mapHolder != null) {
            mapHolder.onPause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mapHolder != null) {
            mapHolder.onResume();
        }
    }

    private void initMapSettings() {
        //地図描画フレームレートの最大値を設定する。ただし、ユーザー操作は含まれない。
        mapView.getMapSettings().setFrameRate(60);
        //地図の表示領域が何を追尾モードを指定する。
        //例えば下記のようにするとユーザーの位置が地図の中心に来るように追尾します。
        mapView.getMapSettings().setFollowerMode(SKMapSettings.SKMapFollowerMode.NONE);
        //地図の言語設定に使用する。
        SKMapInternationalizationSettings skMapInternationalizationSettings = new SKMapInternationalizationSettings();
        skMapInternationalizationSettings.setPrimaryLanguage(SKMaps.SKLanguage.LANGUAGE_RU);
        mapView.getMapSettings().setMapInternationalizationSettings(skMapInternationalizationSettings);
        settingsUserAction();
        settingsMapObject();
        settingsPoi();
        settingsStyle();
        settings3D();
        //以下は使い方がわからない調査中メソッド
        //mapView.getMapSettings().setDrawingOrder();
        //mapView.getMapSettings().setTerrainEnabled(false);
        //mapView.getMapSettings().setTerrainDisabledIfNoElevation(false);
        //mapView.getMapSettings().setOrientationIndicatorType();
        //mapView.getMapSettings().setGeneratedPoisShown(false);
        //mapView.getMapSettings().setImportantPoisShown(true);
    }

    private void settings3D() {
        //地図を3D表示したときのカメラを設定する。
        SK3DCameraSettings sk3DCameraSettings = new SK3DCameraSettings();
        sk3DCameraSettings.setTilt(60);
        sk3DCameraSettings.setCenter(0.9f);
        sk3DCameraSettings.setDistance(10);
        mapView.getMapSettings().setCameraSettings(sk3DCameraSettings);
        //地図を拡大した表示を2Dと3Dのどちらかを指定することができます。
        mapView.getMapSettings().setMapDisplayMode(SKMapSettings.SKMapDisplayMode.MODE_3D);
        //通りの名前をポップアップ風に表示する。true:ポップアップ表示/false:地図上に書き込み
        mapView.getMapSettings().setStreetNamePopupsShown(false);
    }

    private void settingsStyle() {
        // 地図を描画する時に使うスタイルファイルを設定する。
        //SKMaps.zip内にはデフォルトで下記の4styleが存在します。
        String daystyle = "daystyle";
        String grayscalestyle = "grayscalestyle";
        String nightstyle = "nightstyle";
        String outdoorstyle = "outdoorstyle";
        String style = nightstyle;
        //コンストラクタの引数はstyleファイルが存在するフォルダとstyleファイルの名前です。
        SKMapViewStyle skMapViewStyle = new SKMapViewStyle(mapResourcesDirPath + style + "/", style + ".json");
        mapView.getMapSettings().setMapStyle(skMapViewStyle);
    }

    private void settingsPoi() {
        //POIはランドマークのことです。
        //POIの表示非表示を設定する true:表示/false:非表示
        mapView.getMapSettings().setMapPoiIconsShown(true);
        //地図上の都市情報の表示非表示を設定する true:表示/false:非表示
        //country:国名/state:州名/city:都市名/town:街名/village:村名などなど
        mapView.getMapSettings().setCityPoisShown(true);
    }

    private void settingsMapObject() {
        //自転車専用レーンの表示非表示を設定する true:表示/false:非表示
        mapView.getMapSettings().setShowBicycleLanes(true);
        //一方通行の表示非表示を設定する true:表示/false:非表示
        mapView.getMapSettings().setOneWayArrows(true);
        //同一建物内の店舗数の表示非表示を設定する true:表示/false:非表示
        mapView.getMapSettings().setHouseNumbersShown(true);
        //自分の位置の表示非表示を設定する。true:表示/false:非表示
        //初期表示位置はなぜか北極点
        mapView.getMapSettings().setCurrentPositionShown(false);
        //コンパスの表示-true:表示/false:非表示
        mapView.getMapSettings().setCompassShown(true);
        //コンパスの位置を指定する。右上を原点とする。
        mapView.getMapSettings().setCompassPosition(new SKScreenPoint(10, 10));
    }

    private void settingsUserAction() {
        //地図の移動の有効無効を設定する。true:有効/false:無効
        mapView.getMapSettings().setMapPanningEnabled(false);
        //地図の回転の有効無効を設定する true:有効/false:無効
        mapView.getMapSettings().setMapRotationEnabled(false);
        //Zoomの有効無効を設定する。true:有効/false:無効
        mapView.getMapSettings().setMapZoomingEnabled(true);
        //地図の移動に慣性力を付加する。 true:有効/false:無効
        mapView.getMapSettings().setInertiaPanningEnabled(false);
        //地図の回転に慣性力を付加する。 true:有効/false:無効
        mapView.getMapSettings().setInertiaRotatingEnabled(false);
        //地図のズームに慣性力を付加する。 true:有効/false:無効
        mapView.getMapSettings().setInertiaZoomingEnabled(false);
        //アンカーポイントを中心にZoomをするかを設定する。
        // true:アンカー(地図中心)にZoomする。/false:ピンチしている指の間を中心にZoomする。
        mapView.getMapSettings().setZoomWithAnchorEnabled(false);
        //Zoomの上下限を設定するメソッド
        //しかし、どんな値を設定しても特に変化がない。
        mapView.getMapSettings().setZoomLimits(5f, 19f);
        //リファレンスに説明が無く、値を変更しても反応しない。
        //動作しないメソッドなのか?
        mapView.getMapSettings().setMinimumZoomForTapping(0);
    }

    private void settingScaleView() {
        //縮尺の表示を有効にする。
        mapHolder.setScaleViewEnabled(true);
        //縮尺を右下に配置する。
        mapHolder.setScaleViewPosition(0, 80, RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.ALIGN_PARENT_BOTTOM);
        //SKMapScaleViewのインスタンスを取得する。
        SKMapScaleView scaleView = mapHolder.getScaleView();
        //枠線の色を指定する。
        scaleView.setBorderColor(Color.RED);
        //SKMapScaleViewのバーは明るい色と黒い色の二色で塗り分けられます。
        //暗い色を指定します。
        scaleView.setDarkerColor(Color.BLUE);
        //明るい色を指定します。
        scaleView.setLighterColor(Color.GREEN);
        //テキストの色を指定します。
        scaleView.setTextColor(Color.CYAN);
        //縮尺の単位を指定します。キロメーター、マイルフィート、マイルヤードから選択できます。
        scaleView.setDistanceUnit(SKMaps.SKDistanceUnitType.DISTANCE_UNIT_KILOMETER_METERS);
        //各単位のラベルを指定する。
        scaleView.setDistanceUnitLabels("kmのラベル", "mのラベル", "miのラベル", "ftのラベル", "ydのラベル");
        //SKMapScaleViewの自動フェイドアウトアニメーションの有効無効を設定する。true:フェイドアウトする/false:フェイドアウトしない
        scaleView.setFadeOutEnabled(false);
    }

    private void changeAnnotations(SKAnnotation skAnnotation) {
        //地図上に存在するアノテーションのリストを取得する。
        List<skannotation> skAnnotationList = mapView.getAllAnnotations();
        if (skAnnotationList.size() > 5) {
            //地図上のすべてのアノテーションおよびカスタムPOIを削除します。
            mapView.deleteAllAnnotationsAndCustomPOIs();
        }

        if (skAnnotationList.size() % 2 == 0) {
            //タップされたアノテーションを最前面にする。
            mapView.bringToFrontAnnotationWithID(skAnnotation.getUniqueID());
            //タップされたアノテーションのタイプを更新する。
            skAnnotation.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_BLUE);
            mapView.updateAnnotation(skAnnotation);
        } else {
            //タップされたアノテーションを削除する。
            mapView.deleteAnnotation(skAnnotation.getUniqueID());
        }
    }

    /**
    * アノテーションを作成する。
    *
    * @param uniqueID ID
    * @param type     見た目
    * @param minimum  ????
    * @param location 画面座標位置
    * @param pinView  独自VIew
    * @return
    */
    private SKAnnotation createSKAnnotation(int uniqueID, int type, int minimum, SKScreenPoint location, View pinView) {
        //SKAnnotationインスタンスを作成、引数はID
        SKAnnotation skAnnotation = new SKAnnotation(uniqueID);
        //IDの変更
        skAnnotation.setAnnotationType(uniqueID);
        //SKAnnotationの位置指定は緯度経度が必要です。
        //しかし、SKScreenPointは画面上の位置なので緯度経度に変換する必要があります。
        //SKMapSurfaceView#pointToCoordinateを使うことで変換ができます。
        SKCoordinate skCoordinate = mapView.pointToCoordinate(location);
        //SKAnnotationの位置を指定する。
        skAnnotation.setLocation(skCoordinate);
        //ピンの見た目はデフォルトで用意されているアイコン
        //または独自Viewのどちらかをセットできます。
        //どちらもセットされないと何も表示されません。
        //SKAnnotation.SK_ANNOTATION_TYPE_BLUE
        //SKAnnotation.SK_ANNOTATION_TYPE_DESTINATION_FLAG
        //SKAnnotation.SK_ANNOTATION_TYPE_GREEN
        //SKAnnotation.SK_ANNOTATION_TYPE_MARKER
        //SKAnnotation.SK_ANNOTATION_TYPE_PURPLE
        //SKAnnotation.SK_ANNOTATION_TYPE_RED
        //デフォルトのピンをセットする。
        if (pinView == null) {
            skAnnotation.setAnnotationType(type);
        } else {
            //独自Viewを配置する場合はSKAnnotationViewを使う。
            SKAnnotationView skAnnotationView = new SKAnnotationView();
            //Viewをセットする。
            skAnnotationView.setView(pinView);
            //SKAnnotationViewをセットする。
            skAnnotation.setAnnotationView(skAnnotationView);
        }
        //ピンが表示される最小ズームレベルを指定する。
        //いろいろな値を指定しても変わらない。動かない?
        //第一メソッド名がminimumではないのは何故
        skAnnotation.setMininumZoomLevel(minimum);
        //何のオフセットしているのかわからない
        SKScreenPoint skScreenPoint = new SKScreenPoint();
        skScreenPoint.setX(5);
        skScreenPoint.setY(5);
        skAnnotation.setOffset(skScreenPoint);
        return skAnnotation;
    }
}

Androidゲームプログラミング A to Z

新品価格
¥4,968から
(2017/2/27 22:58時点)


AndroidエンジニアのためのモダンJava

新品価格
¥3,456から
(2017/2/27 23:01時点)


AndroidNDKネイティブプログラミング第2版

中古価格
¥1,893から
(2017/2/28 00:04時点)


Androidアプリ開発逆引きレシピ (PROGRAMMER’S RECiPE)

新品価格
¥3,024から
(2017/2/28 00:06時点)


Android Studio ではじめる Android プログラミング入門 第3版 Android Studio 2対応

新品価格
¥3,240から
(2017/2/28 00:11時点)


アプリを作ろう! Android入門 Android Studio版 Android5対応

新品価格
¥2,160から
(2017/2/28 00:31時点)


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)