【问题标题】:Making a Snackbar Without a View?制作没有视图的小吃店?
【发布时间】:2016-12-09 10:51:47
【问题描述】:

我想在用户打开 Google 地图活动后立即显示一个快餐栏,但问题是活动中没有视图可用作活动的第一个参数(在 findViewById()Snackbar.make() )。我在那儿放什么? 这是java类代码:

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();
}
}

另外,这里是活动 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$1.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$5.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)

感谢您的帮助! :)

【问题讨论】:

  • 尝试将“findViewById()”替换为“getWindow().getDecorView().getRootView()”

标签: java android xml crash android-snackbar


【解决方案1】:

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 的更多代码 sn-ps,请参阅 - https://androidlearnersite.wordpress.com/2021/08/03/jetpack-compose-1-0-0-sample-codes/

【讨论】:

    【解决方案2】:

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

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

    对于活动:

    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")
    

    【讨论】:

      【解决方案3】:

      用作extension function

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

      【讨论】:

        【解决方案4】:

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

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

        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())
        

        编码愉快!

        【讨论】:

          【解决方案5】:

          不要这样做。

          为什么?

          为snackbar提供锚点视图的整个想法可能是因为它使我们能够将其放在屏幕上我们最喜欢的位置,

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

          如果您想要一些用于一般消息显示的东西,请选择 toast,它更不容易出错并且外观一致,如果您需要具有交互性的东西,我建议您使用 Alert Dialogue强>

          【讨论】:

            【解决方案6】:

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

            最简单

            SupportMapFragment 扩展类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();
            

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

            如果使用下面的方法获取视图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();
            

            【讨论】:

            • 前两种方法都可以正常工作;但要使用第一个,我需要将整个 Snackbar.make() 动作移动到 onCreate() 中,因为 mapFragment 不是全局变量。发生的一件奇怪的事情是,在这两种情况下,快餐栏文本默认显示为黑色而不是白色,但我可以在代码中手动更改它。
            • 如果使用以下方法获取视图,则会在底部导航栏后面显示消息getWindow().getDecorView().getRootView()
            【解决方案7】:

            添加任何布局并为布局分配一个 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>
            

            在您的活动中使用 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();
            

            【讨论】:

              【解决方案8】:

              我会说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>
              

              【讨论】:

                【解决方案9】:

                正如 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();
                    }
                }
                

                【讨论】:

                • 假设我有一个 BaseActivity、一个 Splashscreen 和一个 mainactivity。现在我想在闪屏和主活动中显示小吃栏。我在 BaseActivity 中调用了你的方法。但它没有在其中任何一个中显示小吃店。
                【解决方案10】:

                在任何活动中试试这个:

                snackbar(findViewById(android.R.id.content),"your text")
                

                【讨论】:

                • 我们如何使用这个
                猜你喜欢
                • 1970-01-01
                • 2019-02-22
                • 2021-10-01
                • 2016-06-28
                • 2020-07-25
                • 2018-05-13
                • 2019-06-09
                • 2018-09-27
                • 1970-01-01
                相关资源
                最近更新 更多