制作没有视图的 Snackbar?

Making a Snackbar Without a View?

我想在用户打开 Google 地图 activity 时立即显示小吃店,但问题是 activity 中没有视图可用作第一个activity 的参数(在 Snackbar.make()findViewById() 中)。我在那里放什么? 这是 java class 代码:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

private GoogleMap mMap;

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

    // Obtain the SupportMapFragment and get notified when the map is ready to be used.
    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    mMap.setBuildingsEnabled(true);
    mMap.getUiSettings().setZoomControlsEnabled(true);
    float cameraZoom = 17;
    LatLng location = new LatLng(43.404032, -80.478184);
    mMap.addMarker(new MarkerOptions().position(location).title("49 McIntyre Place #18, Kitchener, ON N2R 1G3"));
    CameraUpdateFactory.newLatLngZoom(location, cameraZoom);
    Snackbar.make(findViewById(/*WHAT DO I PUT HERE?*/), "Click the pin for more options", Snackbar.LENGTH_LONG).show();
}
}

此外,这里是 activity xml 代码:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="ca.davesautoservice.davesautoservice.MapsActivity" />

最后,这里是堆栈跟踪错误:

08-03 11:42:21.333 3901-3901/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: ca.davesautoservice.davesautoservice, PID: 3901
    java.lang.NullPointerException
        at android.support.design.widget.Snackbar.<init>(Snackbar.java:183)
        at android.support.design.widget.Snackbar.make(Snackbar.java:215)
        at ca.davesautoservice.davesautoservice.MapsActivity.onMapReady(MapsActivity.java:48)
        at com.google.android.gms.maps.SupportMapFragment$zza.zza(Unknown Source)
        at com.google.android.gms.maps.internal.zzo$zza.onTransact(Unknown Source)
        at android.os.Binder.transact(Binder.java:361)
        at xz.a(:com.google.android.gms.DynamiteModulesB:82)
        at maps.ad.u.run(Unknown Source)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:103)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:5333)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644)
        at dalvik.system.NativeStart.main(Native Method)

感谢您的帮助! :)

我看到了一些选项...不确定哪个可以解决您的问题。

最简单

SupportMapFragment 扩展 class android.support.v4.app.Fragment。这样,它就有了一个方法 getView()

Snackbar.make(mapFragment.getView(), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

查找根视图

From this answer,有一种获取根视图的方法:

getWindow().getDecorView().getRootView()

所以,也许,你可以这样做:

Snackbar.make(getWindow().getDecorView().getRootView(), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

注意: 这种方法具有以下评论中提到的副作用:

The message will be shown behind the bottom navigation bar if the following method will be used to get view getWindow().getDecorView().getRootView()

添加虚拟 LinearLayout 以获取视图

老实说,我不确定这个解决方案是否可行。我不确定您是否可以在 Maps 片段上方添加一个 LinearLayout...我认为可以,但是因为我以前从未使用过 Maps API,所以我不确定。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/dummy_layout_for_snackbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment 
        xmlns:map="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="ca.davesautoservice.davesautoservice.MapsActivity" />
</LinearLayout>

然后:

Snackbar.make(findViewById(R.id.dummy_layout_for_snackbar), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

在任何 activity:

中尝试这个
snackbar(findViewById(android.R.id.content),"your text")

正如 Shahab Rauf 指出的那样,通过 getDecorView() 获取视图可能会将快餐栏放在底部导航栏的后面。我使用以下代码:(使用和扩展您的喜悦)

public class BaseActivity extends AppCompatActivity {

    private View getRootView() {
        final ViewGroup contentViewGroup = (ViewGroup) findViewById(android.R.id.content);
        View rootView = null;

        if(contentViewGroup != null)
            rootView = contentViewGroup.getChildAt(0);

        if(rootView == null)
            rootView = getWindow().getDecorView().getRootView();

        return rootView;
    }

    protected void showSnackBarWithOK(@StringRes int res) {
        final View rootView = getRootView();
        if(rootView != null) {
            final Snackbar snackbar = Snackbar.make(getRootView(), res, Snackbar.LENGTH_INDEFINITE);
            snackbar.setAction(R.string.ok, new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    snackbar.dismiss();
                }
            });

            snackbar.show();
        }
    }

    protected void showSnackBar(@StringRes int res) {
        final View rootView = getRootView();
        if(rootView != null)
            Snackbar.make(rootView, res, Snackbar.LENGTH_LONG).show();
    }
}

我会说 findViewById 是最好的方法。因为使用 getWindow().getDecorView().getRootView() 会导致消息显示在底部导航栏后面。

就我而言,我不需要在 XML 中创建虚拟元素。我使用了以下代码

Snackbar.make(findViewById(R.id.container),"This will start payment process.", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();

我的XML文件如下

<android.support.design.widget.CoordinatorLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".HomePatientActivity">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/appbar_padding_top"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_weight="1"
        android:background="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways"
        app:popupTheme="@style/AppTheme.PopupOverlay">

    </android.support.v7.widget.Toolbar>

    <android.support.design.widget.TabLayout
        android:id="@+id/sliding_tabs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:layout_anchorGravity="top"
        app:tabMode="fixed" />

</android.support.design.widget.AppBarLayout>

<android.support.v4.view.ViewPager
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

</android.support.v4.view.ViewPager>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="end|bottom"
    android:layout_margin="@dimen/fab_margin"
    app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

添加任何布局并为布局分配一个 ID 示例如下:

<RelativeLayout
    android:id="@+id/relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/map_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        />

</RelativeLayout>

在您的 activity 中使用 findViewById 查找布局:

RelativeLayout relativeLayout = findViewById(R.id.relativeLayout);

其他布局的示例,只要您为布局分配其 ID:

LinearLayout relativeLayout = findViewById(R.id.linearLayout);
FrameLayout frameLayout = findViewById(R.id.frameLayout);

显示 Snackbar:

Snackbar.make(relativeLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

对于其他布局,只需更改布局示例的名称:

Snackbar.make(linearLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

Snackbar.make(frameLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

不要这样。

为什么?

给 snackbar 一个锚视图的整个想法可能是因为它让我们能够把它放在屏幕上我们最喜欢的地方,

如其他答案中所述,您可以通过多种方式自动将根视图附加到它,使用 getWindow().getDecorView().getRootView() 是其中之一,但它可能会导致此消息出现在底部导航视图,看起来不太好(至少对我而言).

如果您想要显示一般消息的东西,请选择 toast,它更不容易出错并且外观一致,如果您需要交互的东西,我建议 警报对话

好的,以上所有解决方案要么过于复杂,要么无关紧要。

最简单的解决方案是在任何地方使用这个函数

fun showSnackBar(message: String?, activity: Activity?) {
    if (null != activity && null != message) {
        Snackbar.make(
            activity.findViewById(android.R.id.content),
            message, Snackbar.LENGTH_SHORT
        ).show()
    }
}

想在 Activity 中调用这个函数吗?

showSnackBar("Test Message in snackbar", this)

想在 Fragment 中调用这个函数吗?

showSnackBar("Test Message in snackbar", requireActivity())

编码愉快!

用作extension function:

fun Activity?.showSnackBar(message: String) {
    this ?: return
    Snackbar.make(
        this.findViewById(android.R.id.content),
        message, Snackbar.LENGTH_LONG
    ).show()
}

最简单的方法是利用 Kotlin 中 Extention Function 的优势。

第 1 步:创建一个 kotlin 文件,即 Extensions.kt 并粘贴下面的代码

对于Activity:

fun Activity.showSnackBar(msg:String){
    Snackbar.make(this.findViewById(android.R.id.content), msg, Snackbar.LENGTH_SHORT)
        .setAction("Ok"){
        }
        .show()
}

片段:

  fun Fragment.showSnackBar(msg:String){
    Snackbar.make(this.requireActivity().findViewById(android.R.id.content), msg, Snackbar.LENGTH_SHORT)
        .setAction("Ok"){
        }
        .show()
}

第 2 步:像下面这样调用它:

showSnackBar("PASS HERE YOUR CUSTOM MESSAGE")

随着 Jetpack Compose 稳定版发布,现在您不再需要 xml 中的视图。

 @Composable
fun Snackbar() {
    Row {

        if (viewModel.snackbarVisibleState.value) {
            Snackbar(
                action = {
                    Button(onClick = {
                        viewModel.snackbarText.value = ""
                        viewModel.snackbarVisibleState.value = false
                    }) {
                        Text("Ok")
                    }
                },
                modifier = Modifier
                    .padding(8.dp)
                    .align(Alignment.Bottom)
            ) {
                Text(viewModel.snackbarText.value)
                
            }
        }
    }
}

此处 snackbarVisibleState 是视图模型中的一个 MutableState,您需要更改其值才能显示和关闭 Snackbar。

有关 Jetpack Compose 的更多代码片段,请参阅此 - https://androidlearnersite.wordpress.com/2021/08/03/jetpack-compose-1-0-0-sample-codes/