Google Maps Android API v2 - ข้อมูลแบบอินเทอร์แอคทีฟหน้าต่าง (เหมือนใน android google maps ดั้งเดิม)


191

ฉันพยายามกำหนดเองInfoWindowหลังจากคลิกที่เครื่องหมายด้วย Google Maps API v2 ใหม่ ฉันต้องการให้มันดูเหมือนในแอปพลิเคชันแผนที่ดั้งเดิมโดย Google แบบนี้:

ภาพตัวอย่าง

เมื่อฉันมีImageButtonภายในไม่ทำงาน - ทั้งหมดInfoWindowเป็น slected ImageButtonและไม่เพียง ฉันอ่านว่าเป็นเพราะไม่มีViewตัวของมันเอง แต่เป็นภาพรวมดังนั้นแต่ละรายการจึงไม่สามารถแยกความแตกต่างจากกันและกัน

แก้ไข: ในเอกสาร (ขอบคุณDisco S2 ):

ดังที่ได้กล่าวไว้ในส่วนก่อนหน้าของหน้าต่างข้อมูลหน้าต่างข้อมูลไม่ใช่มุมมองแบบสด แต่มุมมองจะแสดงเป็นรูปภาพบนแผนที่ ด้วยเหตุนี้ผู้ฟังที่คุณตั้งค่าในมุมมองจะถูกเพิกเฉยและคุณไม่สามารถแยกความแตกต่างระหว่างเหตุการณ์การคลิกในส่วนต่างๆของมุมมอง คุณไม่ควรวางองค์ประกอบแบบอินเทอร์แอคทีฟเช่นปุ่มช่องทำเครื่องหมายหรืออินพุตข้อความไว้ในหน้าต่างข้อมูลที่กำหนดเองของคุณ

แต่ถ้า Google ใช้มันจะต้องมีวิธีที่จะทำให้มัน ไม่มีใครมีความคิดใด ๆ


"มันใช้งานไม่ได้" ไม่ใช่คำอธิบายที่เป็นประโยชน์อย่างยิ่งสำหรับอาการของคุณ นี่คือตัวอย่างโครงการที่แสดงให้เห็นว่ามีหน้าต่างข้อมูลที่กำหนดเองพร้อมรูปภาพ: github.com/commonsguy/cw-omnibus/tree/master/MapsV2/Popups
CommonsWare

8
@CommonsWare ฉันเขียนด้วยเหตุผลนี้ จากเอกสารต้นฉบับหมายเหตุ: หน้าต่างข้อมูลที่ถูกวาดไม่ใช่มุมมองสด มุมมองถูกแสดงผลเป็นรูปภาพ (โดยใช้View.draw(Canvas)) ... ซึ่งหมายความว่าการเปลี่ยนแปลงใด ๆ ที่ตามมาในมุมมองจะไม่ปรากฏในหน้าต่างข้อมูลบนแผนที่ หากต้องการอัปเดตหน้าต่างข้อมูลในภายหลังนอกจากนี้หน้าต่างข้อมูลจะไม่เคารพการโต้ตอบทั่วไปใด ๆ สำหรับมุมมองปกติเช่นเหตุการณ์การสัมผัสหรือท่าทาง อย่างไรก็ตามคุณสามารถฟังเหตุการณ์การคลิกทั่วไปในหน้าต่างข้อมูลทั้งหมดตามที่อธิบายไว้ในส่วนด้านล่าง ... ในตัวอย่างของคุณเป็นเพียง
มุมมองข้อความ

สวัสดีคุณได้รับปุ่มตำแหน่งของฉันปรากฏใต้แถบการกระทำเมื่ออยู่ในโหมดเต็มหน้าจอได้อย่างไร ขอบคุณ!
tkblackbelt

45
ฉันไม่คิดว่าคำถามนี้ควรถูกปิด มันเป็นคำถามที่ถูกต้องสำหรับปัญหาที่ฉันกำลังเผชิญ
marienke

12
ฉันเห็นสิ่งนี้บ่อยครั้งคำถามที่มีประโยชน์มากที่สุดพร้อมคำตอบถูกปิด คำถามอาจไม่ตรงกับข้อกำหนดที่แน่นอนของคำถามที่เหมาะสม แต่ถ้าเรามีปัญหาที่อธิบายไว้อย่างดีและคำตอบที่อธิบายได้ดีแล้วก็ไม่มีเหตุผลที่จะปิดคำถาม ผู้คนใช้ Stackoverflow เพื่อแลกเปลี่ยนความรู้และปิดเธรดคำถามซึ่งทำงานอย่างแข็งขันและมีประสิทธิผลเพราะ "นอกหัวข้อ" ดูเหมือนจะไม่เป็นความคิดที่ดีสำหรับฉัน ..
John

คำตอบ:


333

ฉันกำลังมองหาวิธีแก้ปัญหานี้ด้วยตัวเองโดยไม่มีโชคดังนั้นฉันจึงต้องม้วนตัวเองซึ่งฉันอยากจะแบ่งปันกับคุณที่นี่ (โปรดแก้ตัวภาษาอังกฤษที่ไม่ดีของฉัน) (มันบ้าไปหน่อยที่จะตอบคนเช็กคนอื่นในภาษาอังกฤษ :-))

PopupWindowสิ่งแรกที่ผมพยายามที่จะใช้ดีเก่า มันค่อนข้างง่าย - คนเดียวต้องฟังOnMarkerClickListenerแล้วแสดงที่กำหนดเองPopupWindowเหนือเครื่องหมาย คนอื่น ๆ ที่นี่ใน StackOverflow แนะนำวิธีแก้ปัญหานี้และจริง ๆ แล้วมันดูค่อนข้างดีตั้งแต่แรกเห็น แต่ปัญหาของการแก้ปัญหานี้จะปรากฏขึ้นเมื่อคุณเริ่มที่จะย้ายแผนที่ คุณต้องย้ายPopupWindowตัวเองอย่างใดซึ่งเป็นไปได้ (โดยฟังเหตุการณ์ onTouch) แต่ IMHO คุณไม่สามารถทำให้ดูดีพอโดยเฉพาะในอุปกรณ์ที่ช้าบางอย่าง หากคุณทำอย่างง่าย ๆ มัน "กระโดด" รอบจากจุดหนึ่งไปยังอีก คุณสามารถใช้แอนิเมชันเพื่อกำจัดการกระโดดเหล่านั้นได้ แต่วิธีนี้PopupWindowจะเป็น "ขั้นตอนหลัง" เสมอซึ่งควรอยู่บนแผนที่ที่ฉันไม่ชอบ

ณ จุดนี้ฉันคิดเกี่ยวกับวิธีแก้ปัญหาอื่น ฉันรู้ว่าจริง ๆ แล้วฉันไม่ต้องการอิสระมากขนาดนั้น - เพื่อแสดงมุมมองที่กำหนดเองของฉันกับความเป็นไปได้ทั้งหมดที่มาพร้อมกับมัน (เช่นแถบความคืบหน้าแบบเคลื่อนไหวเป็นต้น) ฉันคิดว่ามีเหตุผลที่ดีว่าทำไมแม้แต่วิศวกรของ Google ก็ไม่ทำเช่นนี้ในแอป Google Maps ทั้งหมดที่ฉันต้องการคือปุ่มหรือสองปุ่มบน InfoWindow ที่จะแสดงสถานะที่กดและเรียกการกระทำบางอย่างเมื่อคลิก ดังนั้นฉันจึงคิดวิธีแก้ปัญหาที่แยกออกเป็นสองส่วน:

ส่วนที่หนึ่ง: ส่วน
แรกคือการได้รับการคลิกที่ปุ่มเพื่อเรียกการกระทำบางอย่าง ความคิดของฉันเป็นดังนี้:

  1. ทำการอ้างอิงไปยัง infoWindow แบบกำหนดเองที่สร้างขึ้นใน InfoWindowAdapter
  2. ล้อมรอบMapFragment(หรือMapView) ภายใน ViewGroup ที่กำหนดเอง (ของฉันเรียกว่า MapWrapperLayout)
  3. แทนที่MapWrapperLayoutdispatchTouchEvent และ (หากแสดง InfoWindow ในปัจจุบัน) ก่อนกำหนดเส้นทาง MotionEvents ไปยัง InfoWindow ที่สร้างขึ้นก่อนหน้านี้ หากไม่ใช้ MotionEvents (เช่นเพราะคุณไม่ได้คลิกที่พื้นที่ที่สามารถคลิกได้ใน InfoWindow ฯลฯ ) จากนั้น (และจากนั้นเท่านั้น) ปล่อยให้กิจกรรมลงไปที่ Superclass ของ MapWrapperLayout ดังนั้นในที่สุดมันจะถูกส่งไปยังแผนที่

นี่คือซอร์สโค้ดของ MapWrapperLayout:

package com.circlegate.tt.cg.an.lib.map;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

public class MapWrapperLayout extends RelativeLayout {
    /**
     * Reference to a GoogleMap object 
     */
    private GoogleMap map;

    /**
     * Vertical offset in pixels between the bottom edge of our InfoWindow 
     * and the marker position (by default it's bottom edge too).
     * It's a good idea to use custom markers and also the InfoWindow frame, 
     * because we probably can't rely on the sizes of the default marker and frame. 
     */
    private int bottomOffsetPixels;

    /**
     * A currently selected marker 
     */
    private Marker marker;

    /**
     * Our custom view which is returned from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow
     */
    private View infoWindow;    

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

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

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

    /**
     * Must be called before we can route the touch events
     */
    public void init(GoogleMap map, int bottomOffsetPixels) {
        this.map = map;
        this.bottomOffsetPixels = bottomOffsetPixels;
    }

    /**
     * Best to be called from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow. 
     */
    public void setMarkerWithInfoWindow(Marker marker, View infoWindow) {
        this.marker = marker;
        this.infoWindow = infoWindow;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean ret = false;
        // Make sure that the infoWindow is shown and we have all the needed references
        if (marker != null && marker.isInfoWindowShown() && map != null && infoWindow != null) {
            // Get a marker position on the screen
            Point point = map.getProjection().toScreenLocation(marker.getPosition());

            // Make a copy of the MotionEvent and adjust it's location
            // so it is relative to the infoWindow left top corner
            MotionEvent copyEv = MotionEvent.obtain(ev);
            copyEv.offsetLocation(
                -point.x + (infoWindow.getWidth() / 2), 
                -point.y + infoWindow.getHeight() + bottomOffsetPixels);

            // Dispatch the adjusted MotionEvent to the infoWindow
            ret = infoWindow.dispatchTouchEvent(copyEv);
        }
        // If the infoWindow consumed the touch event, then just return true.
        // Otherwise pass this event to the super class and return it's result
        return ret || super.dispatchTouchEvent(ev);
    }
}

ทั้งหมดนี้จะทำให้มุมมองภายใน InfoView "สด" อีกครั้ง - OnClickListeners จะเริ่มเรียก ฯลฯ

ส่วนที่สอง: ปัญหาที่เหลือคือแน่นอนคุณไม่สามารถเห็นการเปลี่ยนแปลง UI ของ InfoWindow ของคุณบนหน้าจอ คุณต้องโทรหา Marker.showInfoWindow ด้วยตนเอง ทีนี้ถ้าคุณทำการเปลี่ยนแปลงแบบถาวรใน InfoWindow ของคุณ (เช่นการเปลี่ยนป้ายชื่อปุ่มของคุณเป็นอย่างอื่น) นี่เป็นการดีพอ

แต่การแสดงสถานะปุ่มกดหรือสิ่งที่มีลักษณะนั้นมีความซับซ้อนมากขึ้น ปัญหาแรกคือ (อย่างน้อย) ฉันไม่สามารถทำให้ InfoWindow แสดงสถานะการกดปุ่มปกติ แม้ว่าฉันจะกดปุ่มเป็นเวลานาน ๆ มันก็ยังคงอยู่ในหน้าจอ ฉันเชื่อว่านี่เป็นสิ่งที่จัดการโดยกรอบแผนที่ซึ่งอาจทำให้แน่ใจว่าจะไม่แสดงสถานะชั่วคราวในหน้าต่างข้อมูล แต่ฉันอาจผิดฉันไม่ได้พยายามค้นหามัน

สิ่งที่ฉันทำคือการแฮ็คที่น่ารังเกียจอีกอย่างหนึ่ง - ฉันแนบOnTouchListenerปุ่มและเปลี่ยนพื้นหลังด้วยตนเองเมื่อปุ่มถูกกดหรือปล่อยไปยัง drawable ที่กำหนดเองสองอัน - อันหนึ่งมีปุ่มอยู่ในสถานะปกติและอีกอันอยู่ในสถานะกด มันไม่ได้ดีนัก แต่ใช้งานได้ดี :) ตอนนี้ฉันสามารถเห็นการสลับปุ่มระหว่างสถานะปกติกับสถานะกดบนหน้าจอ

ยังคงมีอีกหนึ่งข้อผิดพลาดล่าสุด - หากคุณคลิกปุ่มเร็วเกินไปจะไม่แสดงสถานะที่กด - เพียงแค่ยังคงอยู่ในสถานะปกติ (แม้ว่าการคลิกตัวเองจะถูกไล่ออก อย่างน้อยนี่เป็นวิธีแสดงบน Galaxy Nexus ของฉัน ดังนั้นสิ่งสุดท้ายที่ฉันทำก็คือฉันล่าช้าปุ่มในสถานะกดเล็กน้อย นี่ก็ค่อนข้างน่าเกลียดและฉันก็ไม่แน่ใจว่ามันจะทำงานกับอุปกรณ์เก่าที่ช้ากว่าได้อย่างไร คุณสามารถลองด้วยตัวเอง - เมื่อคุณคลิก InfoWindow ทั้งหมดมันจะยังคงอยู่ในสถานะกดอีกเล็กน้อยจากนั้นปุ่มปกติจะทำ (อีกครั้ง - อย่างน้อยบนโทรศัพท์ของฉัน) และนี่คือวิธีการใช้งานจริงแม้ในแอป Google แผนที่ดั้งเดิม

อย่างไรก็ตามฉันได้เขียนคลาสที่กำหนดเองซึ่งจัดการกับการเปลี่ยนแปลงสถานะของปุ่มและสิ่งอื่น ๆ ทั้งหมดที่ฉันพูดถึงดังนั้นนี่คือรหัส:

package com.circlegate.tt.cg.an.lib.map;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.google.android.gms.maps.model.Marker;

public abstract class OnInfoWindowElemTouchListener implements OnTouchListener {
    private final View view;
    private final Drawable bgDrawableNormal;
    private final Drawable bgDrawablePressed;
    private final Handler handler = new Handler();

    private Marker marker;
    private boolean pressed = false;

    public OnInfoWindowElemTouchListener(View view, Drawable bgDrawableNormal, Drawable bgDrawablePressed) {
        this.view = view;
        this.bgDrawableNormal = bgDrawableNormal;
        this.bgDrawablePressed = bgDrawablePressed;
    }

    public void setMarker(Marker marker) {
        this.marker = marker;
    }

    @Override
    public boolean onTouch(View vv, MotionEvent event) {
        if (0 <= event.getX() && event.getX() <= view.getWidth() &&
            0 <= event.getY() && event.getY() <= view.getHeight())
        {
            switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: startPress(); break;

            // We need to delay releasing of the view a little so it shows the pressed state on the screen
            case MotionEvent.ACTION_UP: handler.postDelayed(confirmClickRunnable, 150); break;

            case MotionEvent.ACTION_CANCEL: endPress(); break;
            default: break;
            }
        }
        else {
            // If the touch goes outside of the view's area
            // (like when moving finger out of the pressed button)
            // just release the press
            endPress();
        }
        return false;
    }

    private void startPress() {
        if (!pressed) {
            pressed = true;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawablePressed);
            if (marker != null) 
                marker.showInfoWindow();
        }
    }

    private boolean endPress() {
        if (pressed) {
            this.pressed = false;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawableNormal);
            if (marker != null) 
                marker.showInfoWindow();
            return true;
        }
        else
            return false;
    }

    private final Runnable confirmClickRunnable = new Runnable() {
        public void run() {
            if (endPress()) {
                onClickConfirmed(view, marker);
            }
        }
    };

    /**
     * This is called after a successful click 
     */
    protected abstract void onClickConfirmed(View v, Marker marker);
}

นี่คือไฟล์เค้าโครง InfoWindow แบบกำหนดเองที่ฉันใช้:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginRight="10dp" >

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Title" />

        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="snippet" />

    </LinearLayout>

    <Button
        android:id="@+id/button" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

ทดสอบไฟล์โครงร่างกิจกรรม ( MapFragmentอยู่ภายในMapWrapperLayout):

<com.circlegate.tt.cg.an.lib.map.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map_relative_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.MapFragment" />

</com.circlegate.tt.cg.an.lib.map.MapWrapperLayout>

และในที่สุดซอร์สโค้ดของกิจกรรมทดสอบซึ่งจับจ้องทั้งหมดนี้เข้าด้วยกัน:

package com.circlegate.testapp;

import com.circlegate.tt.cg.an.lib.map.MapWrapperLayout;
import com.circlegate.tt.cg.an.lib.map.OnInfoWindowElemTouchListener;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {    
    private ViewGroup infoWindow;
    private TextView infoTitle;
    private TextView infoSnippet;
    private Button infoButton;
    private OnInfoWindowElemTouchListener infoButtonListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final MapFragment mapFragment = (MapFragment)getFragmentManager().findFragmentById(R.id.map);
        final MapWrapperLayout mapWrapperLayout = (MapWrapperLayout)findViewById(R.id.map_relative_layout);
        final GoogleMap map = mapFragment.getMap();

        // MapWrapperLayout initialization
        // 39 - default marker height
        // 20 - offset between the default InfoWindow bottom edge and it's content bottom edge 
        mapWrapperLayout.init(map, getPixelsFromDp(this, 39 + 20)); 

        // We want to reuse the info window for all the markers, 
        // so let's create only one class member instance
        this.infoWindow = (ViewGroup)getLayoutInflater().inflate(R.layout.info_window, null);
        this.infoTitle = (TextView)infoWindow.findViewById(R.id.title);
        this.infoSnippet = (TextView)infoWindow.findViewById(R.id.snippet);
        this.infoButton = (Button)infoWindow.findViewById(R.id.button);

        // Setting custom OnTouchListener which deals with the pressed state
        // so it shows up 
        this.infoButtonListener = new OnInfoWindowElemTouchListener(infoButton,
                getResources().getDrawable(R.drawable.btn_default_normal_holo_light),
                getResources().getDrawable(R.drawable.btn_default_pressed_holo_light)) 
        {
            @Override
            protected void onClickConfirmed(View v, Marker marker) {
                // Here we can perform some action triggered after clicking the button
                Toast.makeText(MainActivity.this, marker.getTitle() + "'s button clicked!", Toast.LENGTH_SHORT).show();
            }
        }; 
        this.infoButton.setOnTouchListener(infoButtonListener);


        map.setInfoWindowAdapter(new InfoWindowAdapter() {
            @Override
            public View getInfoWindow(Marker marker) {
                return null;
            }

            @Override
            public View getInfoContents(Marker marker) {
                // Setting up the infoWindow with current's marker info
                infoTitle.setText(marker.getTitle());
                infoSnippet.setText(marker.getSnippet());
                infoButtonListener.setMarker(marker);

                // We must call this to set the current marker and infoWindow references
                // to the MapWrapperLayout
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });

        // Let's add a couple of markers
        map.addMarker(new MarkerOptions()
            .title("Prague")
            .snippet("Czech Republic")
            .position(new LatLng(50.08, 14.43)));

        map.addMarker(new MarkerOptions()
            .title("Paris")
            .snippet("France")
            .position(new LatLng(48.86,2.33)));

        map.addMarker(new MarkerOptions()
            .title("London")
            .snippet("United Kingdom")
            .position(new LatLng(51.51,-0.1)));
    }

    public static int getPixelsFromDp(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f);
    }
}

แค่นั้นแหละ. จนถึงตอนนี้ฉันทดสอบบน Galaxy Nexus ของฉัน (4.2.1) และ Nexus 7 (เช่น 4.2.1) เท่านั้นฉันจะลองใช้โทรศัพท์ Gingerbread บางรุ่นเมื่อฉันมีโอกาส ข้อ จำกัด ที่ฉันพบจนถึงขณะนี้คือคุณไม่สามารถลากแผนที่จากที่ที่ปุ่มของคุณอยู่บนหน้าจอและย้ายแผนที่ไปรอบ ๆ มันอาจจะเอาชนะได้ แต่ตอนนี้ฉันสามารถอยู่กับมันได้

ฉันรู้ว่านี่เป็นแฮ็คที่น่าเกลียด แต่ฉันก็ไม่พบอะไรที่ดีขึ้นและฉันต้องการรูปแบบการออกแบบที่แย่มากว่านี่จะเป็นเหตุผลที่จะกลับไปที่เฟรมเวิร์ก v1 (ซึ่ง btw ฉันต้องการหลีกเลี่ยง สำหรับแอปใหม่ที่มีแฟรกเมนต์ ฯลฯ ) ฉันไม่เข้าใจว่าเพราะเหตุใด Google จึงไม่เสนอวิธีที่เป็นทางการให้นักพัฒนามีปุ่มใน InfoWindows มันเป็นรูปแบบการออกแบบที่ใช้กันทั่วไปยิ่งกว่านั้นรูปแบบนี้จะถูกใช้แม้ในแอปอย่างเป็นทางการของ Google Maps :) ฉันเข้าใจเหตุผลที่ว่าทำไมพวกเขาไม่สามารถทำให้มุมมองของคุณ "สด" ใน InfoWindows - นี่อาจเป็นการฆ่าประสิทธิภาพเมื่อย้ายและเลื่อนแผนที่รอบ ๆ แต่ควรมีวิธีการบรรลุผลนี้โดยไม่ใช้มุมมอง


6
ทางออกที่น่าสนใจ ฉันต้องการลองสิ่งนี้ ประสิทธิภาพเป็นอย่างไร
แพทริค

10
จำเป็นต้องเปลี่ยนview.setBackground(bgDrawablePressed);กับ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); }else{ view.setBackground(bgDrawablePressed); }การทำงานใน Android Gingerbread หมายเหตุ: มีสิ่งหนึ่งที่เกิดขึ้นมากขึ้นของview.setBackgroundในรหัส
Balaji

4
KK_07k11A0585: ดูเหมือนว่าการใช้งาน InfoWindowAdapter ของคุณอาจผิด มีสองวิธีในการแทนที่: getInfoWindow () และ getInfoContents () เฟรมเวิร์กเรียกเมธอด getInfoWindow () และ (เท่านั้น) ก่อนถ้ามันคืนค่า null มันเรียก getInfoContents () หากคุณส่งคืนมุมมองบางส่วนจาก getInfoWindow กรอบงานจะวางภายในกรอบข้อมูลหน้าต่างเริ่มต้นซึ่งในกรณีของคุณคุณไม่ต้องการ ดังนั้นเพียงแค่คืนค่า null จาก getInfoWindow และคืนค่ามุมมองจาก getInfoContent และควรจะใช้ได้
007

18
หากใครสนใจฉันใช้โซลูชันนี้ในแอพของฉันเอง - CG Transit (คุณสามารถหาได้จาก Google Play) หลังจากผ่านไปสองเดือนมีผู้ใช้หลายหมื่นคนใช้งานและยังไม่มีใครบ่นเลย
007

2
คุณจะกำหนดออฟเซ็ตด้านล่างได้อย่างไรหากคุณไม่ได้ใช้ infowindow มาตรฐาน
Rarw

12

ฉันเห็นว่าคำถามนี้เก่าแล้ว แต่ก็ยัง ...

เราสร้างห้องสมุด sipmle ที่ บริษัท ของเราเพื่อให้บรรลุสิ่งที่ต้องการ - หน้าต่างข้อมูลแบบโต้ตอบที่มีมุมมองและทุกอย่าง คุณสามารถตรวจสอบออกบน GitHub

ฉันหวังว่ามันจะช่วย :)


เป็นไปได้ไหมที่จะใช้กับกลุ่ม?
gcolucci

9

นี่คือปัญหาของฉัน ฉันสร้างAbsoluteLayoutภาพซ้อนทับซึ่งมีหน้าต่างข้อมูล (มุมมองปกติพร้อมความสามารถในการโต้ตอบและการวาดภาพทุกบิต) จากนั้นฉันก็เริ่มHandlerที่ประสานตำแหน่งของหน้าต่างข้อมูลกับตำแหน่งของจุดบนแผนที่ทุก ๆ 16 มิลลิวินาที ฟังดูบ้า แต่ใช้งานได้จริง

วิดีโอสาธิต: https://www.youtube.com/watch?v=bT9RpH4p9mU (พิจารณาว่าประสิทธิภาพลดลงเนื่องจากโปรแกรมจำลองและการบันทึกวิดีโอทำงานพร้อมกัน)

รหัสของการสาธิต: https://github.com/deville/info-window-demo

บทความที่ให้รายละเอียด (ภาษารัสเซีย): http://habrahabr.ru/post/213415/


สิ่งนี้จะทำให้ช้าลง / ล่าช้าในการเลื่อนแผนที่
Shane Ekanayake

2

สำหรับผู้ที่ไม่สามารถchoose007'sตอบรับและทำงานได้

ถ้าclickListenerทำงานไม่ถูกต้องอยู่ตลอดเวลาในchose007'sการแก้ปัญหาพยายามที่จะใช้แทนView.onTouchListener clickListenerเหตุการณ์สัมผัสจับใช้ใด ๆ ของการกระทำ หรือACTION_UP ACTION_DOWNด้วยเหตุผลบางแผนที่ทำให้เกิดพฤติกรรมบางลางเมื่อเยี่ยงอย่างไปinfoWindowclickListeners

infoWindow.findViewById(R.id.my_view).setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
          int action = MotionEventCompat.getActionMasked(event);
          switch (action){
                case MotionEvent.ACTION_UP:
                    Log.d(TAG,"a view in info window clicked" );
                    break;
                }
                return true;
          }

แก้ไข: นี่คือวิธีที่ฉันทำทีละขั้นตอน

ก่อนอื่นให้ขยาย infowindow ของคุณ (ตัวแปรทั่วโลก) ที่ใดที่หนึ่งในกิจกรรม / ส่วนของคุณ ฉันอยู่ในส่วน นอกจากนี้ยังรับประกันว่ามุมมองรูทในเค้าโครง infowindow ของคุณคือ linearlayout (ด้วยเหตุผลบางอย่าง relativelayout กำลังถ่ายเต็มหน้าจอใน infowindow)

infoWindow = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.info_window, null);
/* Other global variables used in below code*/
private HashMap<Marker,YourData> mMarkerYourDataHashMap = new HashMap<>();
private GoogleMap mMap;
private MapWrapperLayout mapWrapperLayout;

จากนั้นในการโทรกลับ onMapReady ของ Google แผนที่ android api (ทำตามนี้หากคุณไม่ทราบว่า onMapReady คือแผนที่> เอกสาร - เริ่มต้นใช้งาน )

   @Override
    public void onMapReady(GoogleMap googleMap) {
       /*mMap is global GoogleMap variable in activity/fragment*/
        mMap = googleMap;
       /*Some function to set map UI settings*/ 
        setYourMapSettings();

MapWrapperLayoutการเริ่มต้น http://stackoverflow.com/questions/14123243/google-maps-android-api-v2- แบบโต้ตอบอินโฟกราฟิกอินฟอร์ดอินเหมือนต้นฉบับ android-go / 15040761 # 15040761 39 - ความสูงเครื่องหมายเริ่มต้นที่ 20 - ชดเชยระหว่าง InfoWindow edge edge เริ่มต้นและขอบล่างของเนื้อหา * /

        mapWrapperLayout.init(mMap, Utils.getPixelsFromDp(mContext, 39 + 20));

        /*handle marker clicks separately - not necessary*/
       mMap.setOnMarkerClickListener(this);

       mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
                @Override
                public View getInfoWindow(Marker marker) {
                    return null;
                }

            @Override
            public View getInfoContents(Marker marker) {
                YourData data = mMarkerYourDataHashMap.get(marker);
                setInfoWindow(marker,data);
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });
    }

วิธี SetInfoWindow

private void setInfoWindow (final Marker marker, YourData data)
            throws NullPointerException{
        if (data.getVehicleNumber()!=null) {
            ((TextView) infoWindow.findViewById(R.id.VehicelNo))
                    .setText(data.getDeviceId().toString());
        }
        if (data.getSpeed()!=null) {
            ((TextView) infoWindow.findViewById(R.id.txtSpeed))
                    .setText(data.getSpeed());
        }

        //handle dispatched touch event for view click
        infoWindow.findViewById(R.id.any_view).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = MotionEventCompat.getActionMasked(event);
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        Log.d(TAG,"any_view clicked" );
                        break;
                }
                return true;
            }
        });

คลิกที่เครื่องหมายมือจับแยกกัน

    @Override
    public boolean onMarkerClick(Marker marker) {
        Log.d(TAG,"on Marker Click called");
        marker.showInfoWindow();
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(marker.getPosition())      // Sets the center of the map to Mountain View
                .zoom(10)
                .build();
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),1000,null);
        return true;
    }

คุณสามารถแสดงรหัสของคุณให้ฉันได้ไหม ฉันสามารถบันทึกกิจกรรมการสัมผัสได้สำเร็จ แต่ไม่คลิกเหตุการณ์ดังนั้นฉันจึงใช้การดำเนินการนี้ นอกจากนี้คุณต้องใช้โซลูชันของ select007 อย่างถูกต้อง
Manish Jangid

ฉันแบ่งปันการใช้งานที่สมบูรณ์ของฉัน แจ้งให้เราทราบหากคุณมีข้อสงสัยใด ๆ
Manish Jangid

0

เพียงแค่การเก็งกำไรฉันมีประสบการณ์ไม่เพียงพอที่จะลอง ... ) -:

เนื่องจาก GoogleMap เป็นแฟรกเมนต์จึงเป็นไปได้ที่จะจับเครื่องหมายบนเหตุการณ์คลิกและแสดงมุมมองส่วนที่กำหนดเอง ส่วนของแผนที่จะยังคงปรากฏบนพื้นหลัง มีใครลองบ้างไหม มีเหตุผลอะไรที่ทำให้มันใช้งานไม่ได้?

ข้อเสียคือชิ้นส่วนของแผนที่จะถูกตรึงบนพื้นด้านหลังจนกระทั่งชิ้นส่วนข้อมูลที่กำหนดเองกลับมาควบคุมมัน


5
ฉันไม่คิดว่ามันน่าสนใจที่จะโพสต์การเก็งกำไรเป็นคำตอบ บางทีมันควรจะเป็นความคิดเห็นในคำถามเดิม แค่ความคิดของฉัน
Johan Karlsson

1
ห้องสมุดนี้github.com/Appolica/InteractiveInfoWindowAndroidทำสิ่งที่คล้ายกัน
Deyan Genovski

-2

ฉันได้สร้างตัวอย่างโครงการ android studio สำหรับคำถามนี้

ภาพหน้าจอที่แสดงผล: -

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ดาวน์โหลดซอร์สโค้ดโครงการแบบสมบูรณ์คลิก ที่นี่

โปรดทราบ:คุณต้องเพิ่มคีย์ API ใน Androidmanifest.xml


5
ควรแบ่งปันรหัสของคุณผ่าน Github / gist แทนที่จะเป็นไฟล์ zip ดังนั้นเราสามารถตรวจสอบได้โดยตรง
Noundla Sandeep

พบข้อผิดพลาดเล็กน้อยเมื่อคุณกดปุ่มบางปุ่มทุกปุ่มจะถูกคลิก
Morozov

3
@Noundla & คนอื่น ๆ : ไม่ต้องกังวลกับการดาวน์โหลดรหัสไปรษณีย์ของเขา เป็นสำเนาที่ถูกต้องของคำตอบที่ยอมรับสำหรับคำถามนี้
Ganesh Mohan

1
@ ganesh2shiv ถูกต้องรหัสซิปไม่มีประโยชน์ คุณไม่ควรแนบสำเนารหัสวาง
Onkar Nene

-7

มันง่ายจริงๆ

googleMap.setInfoWindowAdapter(new InfoWindowAdapter() {

            // Use default InfoWindow frame
            @Override
            public View getInfoWindow(Marker marker) {              
                return null;
            }           

            // Defines the contents of the InfoWindow
            @Override
            public View getInfoContents(Marker marker) {

                // Getting view from the layout file info_window_layout
                View v = getLayoutInflater().inflate(R.layout.info_window_layout, null);

                // Getting reference to the TextView to set title
                TextView note = (TextView) v.findViewById(R.id.note);

                note.setText(marker.getTitle() );

                // Returning the view containing InfoWindow contents
                return v;

            }

        });

เพียงเพิ่มโค้ดด้านบนในชั้นเรียนของคุณซึ่งคุณใช้ GoogleMap R.layout.info_window_layout เป็นเลย์เอาต์ที่กำหนดเองของเราที่แสดงมุมมองที่จะเข้ามาแทนที่ infowindow ฉันเพิ่งเพิ่มมุมมองข้อความที่นี่ คุณสามารถเพิ่มมุมมอง additonal ที่นี่เพื่อให้มันเหมือนกับสแน็ปตัวอย่าง info_window_layout ของฉันคือ

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:orientation="vertical" 
    >

        <TextView 
        android:id="@+id/note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

ฉันหวังว่ามันจะช่วย เราสามารถหาตัวอย่างการทำงานของ infowindow ที่กำหนดเองที่ http://wptrafficanalyzer.in/blog/customizing-infowindow-contents-in-google-map-android-api-v2-using-infowindowadapter/#comment-39731

แก้ไข: รหัสนี้จะแสดงวิธีที่เราสามารถเพิ่มมุมมองที่กำหนดเองใน infoWindow รหัสนี้ไม่ได้จัดการกับการคลิกในรายการมุมมองที่กำหนดเอง ดังนั้นจึงใกล้เคียงกับคำตอบ แต่ไม่ใช่คำตอบที่แน่นอนว่าทำไมมันไม่ได้รับการยอมรับว่าเป็นคำตอบ


คุณแทนที่ getInfoContents และ getInfoWindow ที่คุณสร้างเครื่องหมายหรือไม่ ตัวอย่างเช่นหากฉันเพิ่มเครื่องหมายประเภทต่างๆและใช้วิธีการที่แตกต่างกันฉันจะแทนที่ภายในเนื้อความของแต่ละวิธีที่ฉันสร้างเครื่องหมายที่แตกต่างกันหรือไม่
Rarw

ใช่ฉันแทนที่ getInfoContents และ getInfoWindow
Zeeshan Mirza

เพิ่งลองสิ่งนี้แล้วมันก็ใช้งานได้เช่นเดียวกับพองตัวเลย์เอาต์ XML อื่น ๆ - งานที่ดี
Rarw

31
ดูเหมือนว่าคุณไม่ได้อ่านคำถามของฉัน ฉันรู้วิธีสร้างมุมมองที่กำหนดเองภายใน infowindow สิ่งที่ฉันไม่รู้คือวิธีการสร้างปุ่มที่กำหนดเองภายในและฟังเหตุการณ์การคลิกของมัน
user1943012

5
วิธีนี้ไม่ตอบคำถามเดิม
biddulph.r
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.