问题 创建一个真正的启动画面


如何在Android中制作真正的启动画面?我不想要定时器或延迟。只是在您的应用程序加载之前显示的启动画面。


7357
2018-06-29 11:08


起源

可以看看: blog.blundell-apps.com/tut-splashscreen-with-progress-bar - Blundell


答案:


就像是

public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.splash);

    handler = new Handler();

    new AsyncTask<Void, Void, Void>() {

    @Override
    protected Void doInBackground(Void... params) {
            //Do some heavy stuff
            return null;
        } 

        @Override
        public void onPostExecute(Void result){
            handler.post(new Runnable(){
                 @Override
                 public void run(){
                     setContentView(R.layout.main);
                 }
            });
        }
   }.execute();
}

3
2018-06-29 11:17



如果重要的东西是精确的setContentView(R.layout.main)怎么办? - slipbull
你永远不应该在活动上调用setContentView两次。我建议调用startActivity(你要在启动后加载的活动)而不是setContentView。 - jsb
这对我不起作用。屏幕粘到第一个飞溅,而不是设置到第二个加载屏幕。我使用了几乎相同的代码,除了我在run方法上没有@Override标记,因为eclipse将其删除为quickFix说:new new Runnable(){}的方法run()必须覆盖超类方法 - kaid
究竟是什么启动了AsyncTask? - kaid
我已经添加 .execute() 到代码,复制粘贴时一定出错了:) - nhaarman


带有代码的解决方案出现在过去六周似乎在许多测试设备上完美运行。

但是,在进入完整的启动画面之前,应该考虑一些预备知识。

首先,如果您可以通过立即调出应用程序的主视图来避免需要启动画面,让用户立即访问您的应用程序,这是您最好的选择。

您通常可以通过立即调出主显示器的图形,然后创建工作线程来执行任何耗时的初始化任务(例如加载应用程序始终使用的表)来实现此目的。

但是,它可能是 图像 你的主要观点 他们自己 设置和显示需要很长时间,并且您希望在初始化期间看到其他内容。

现在,如果您的主要活动具有简单(例如,默认),浅色或黑色,非透明背景,那么将立即提供至少确认 某物 在用户启动您的应用时发生。我个人发现作为原始“启动”显示工作的背景主题包括以下内容(要添加到清单文件中主要活动的活动标记):

android:theme="@style/Theme.Light.NoTitleBar"
android:theme="@style/Theme.Black.NoTitleBar"

在这方面我会注意到,如果你的活动背景需要任何类型的位图,动画或超出默认主题的其他可绘制(或如上所示的简单的浅色或黑色主题),我的经验是活动背景将是  显示,直到您的主视图仍然显示,所以只是将您的活动背景更改为自己  你的飞溅显示器(根据我的经验)没有比主屏幕提供的更快速的响应。

虽然上面这些简单的主题可以作为原始的“泼溅”使用,但也许您认为简单的浅色或黑色活动背景太过于提示您的应用已启动的提示,并且您希望显示应用的名称或徽标。用户在等。或者,也许是您的活动背景 必须 是 透明,因为你希望能够用自己的应用程序的视图覆盖其他应用程序(当然,这样的透明背景在启动时是不可见的,因此不会提示用户您的应用程序已经启动)。

如果在考虑了上面提到的所有替代方案之后,你仍然认为你需要一个闪屏,这是我个人发现的一种非常好的方法。

对于这种方法,您需要定义一个扩展LinearLayout的新类。你需要自己的课程的原因是因为你需要得到你的启动画面实际显示的肯定确认,这样你就可以立即继续显示你的主视图,而不需要只有一些计时器 猜测 你的启动画面出现多长时间。在这方面我会注意到,如果在显示启动视图后过快地开始显示主视图,则永远不会看到启动视图;使用这种方法避免了这种可能性。

以下是此类的示例:

public class SplashView extends LinearLayout {

    public interface SplashEvents {
        //This event is signaled after the splash and all of its child views, 
        // if any, have been drawn.
        // As currently implemented, it will trigger BEFORE any scrollbars are drawn.
        // We are assuming that there will BE no scrollbars on a SplashView.
        public void onSplashDrawComplete();
    }

    private SplashEvents splashEventHandler = null;

    public void setSplashEventHandler(SplashEvents splashEventHandler) {
        this.splashEventHandler = splashEventHandler;
    }

    private void fireSplashDrawCompleteEvent() {
        if(this.splashEventHandler != null) {
            this.splashEventHandler.onSplashDrawComplete();
        }
    }

    public SplashView(Context context) {
        super(context);

        //This is set by default for a LinearLayout, but just making sure!
        this.setWillNotDraw(true);

        //If the cache is not enabled, then I think that helps to ensure that
        // dispatchDraw override WILL
        // get called.  Because if caching were enabled, then the 
        //drawing might not occur.
        this.setDrawingCacheEnabled(false);

        setGravity(Gravity.CENTER);

        //This splices in your XML definition (see below) to the SplashView layout
        LayoutInflater.from(context).inflate(R.layout.splashscreen, this, true);
    }


    @Override
    protected void dispatchDraw(Canvas canvas) {
        //Draw any child views
        super.dispatchDraw(canvas);

        //Signal client objects (in this case, your main activity) that 
            // we have finished initializing and drawing the view.
        fireSplashDrawCompleteEvent();
    }
}

因为我们从视图中加载XML,所以我们需要使用a在XML中定义它 <merge> 标记为“拼接”在XML定义的元素中作为SplashView类的子元素。这是一个示例(放在应用程序的res / layout文件夹中),您可以根据自己的需要进行定制:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_horizontal"
    >
    <TextView  android:id="@+id/tvHeading"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:gravity="center"
        android:textSize="30dp"
        android:textStyle="bold"
        android:text="Loading..."
        android:layout_weight="1.0"
        android:textColor="#00ff00"
        android:background="#AA000000"
        />
</merge>

请注意,TextView定义为半透明的黑色背景,因此它会导致启动器显示变暗,文本“Loading ...”以绿色叠加在顶部。

剩下的就是在主活动的onCreate()方法中编辑以下内容(及以上):

private Handler uiThreadHandler = new Handler();

@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Create an instance of the splash view, and perform a setContentView()
    SplashView splashView = new SplashView(this);

    //Doing this allows you to access the "this" pointer of the main 
        // activity inside the Runnable below.
    final main mainThis = this;

    // Set an event handler on the SplashView object, so that as soon 
    // as it completes drawing we are
    // informed.  In response to that cue, we will *then* put up the main view, 
    // replacing the content view of the main activity with that main view.
    splashView.setSplashEventHandler(new SplashView.SplashEvents() {
        @Override
        public void onSplashDrawComplete() {
            //Post the runnable that will put up the main view
            uiThreadHandler.post(new Runnable() {
                @Override
                    public void run() {
                        //This method is where you will have moved 
                        // the entire initialization of your app's 
                        // main display, which normally would have been 
                        // in your onCreate() method prior to adding the 
                        // splash display.
                        launchMainView(mainThis, savedInstanceState);
                    }
            });
        }
    });

    //This will cause your splash view to draw.  When it finishes, it will trigger the code above.
    this.setContentView(splashView);

    //At this point, do *not* move directly on to performing a setContentView() on your main view.
    // If you do, you will never see the splash view at all.
    // You simply wait for the splash view to signal you that it has completed drawing itself, and
    // *then* call launchMainView(), which will itself call setContentView() again, passing it
    // your main view.
}

//Here is a stripped-down version of launchMainView().  You will typically have some additional
// initializations here - whatever might have been present originally in your onCreate() method.
public void launchMainView(main mainThis, Bundle savedInstanceState) {
    myRootView = new MyRootView(mainThis);

    setContentView(myRootView);
}

上述方法对我来说非常有效。我使用它仅针对API级别8,并且已经在运行Android 2.2.1,2.3.3和4.0.1(ICS)的各种设备(包括手机和平板电脑)上测试了该代码。

警告:

上述方法的潜在责任是,对于某些情况的组合,可能会出现飞溅视图  表示它已完成,因此飞溅将“卡在”主显示屏上,没有主视图可以替换它。这从来没有发生在我身上,但是我想在这里征求关于上面SplashView中的dispatchDraw()重写的意见。  被叫。我对触发dispatchDraw()的代码进行了视觉检查,并且在我看来我在SplashView构造函数中完成的初始化时,它看起来好像总会被调用。

如果有人有更好的方法来覆盖同样的目的,我会很感激听到它。令我感到惊讶的是,当一个视图完成显示时,我无法找到专门用于触发的任何覆盖,因此,如果存在并且我以某种方式错过了它,请在下面发表评论。评论肯定了这种方法  工作也很受欢迎!


8
2018-04-18 02:56



非常适合我,我喜欢你的解决方案,希望将来不会有任何错误。谢谢! - idish
同样,我在多个应用程序中使用这种方法没有任何问题,我自己在从Froyo到Jelly Bean的设备上进行测试,公开发布了好几个月而没有任何抱怨。 - Carl
为什么不使用“onDraw”,只在那里调用你的函数一次(使用一个标志来保护它不被多次调用)。 - android developer
绘制了飞溅视图的子视图 后 调用splash视图自己的onDraw()方法以便显示在它前面,因此从onDraw()触发onSplashDrawComplete()会在显示初始视图的任何子视图之前启动主视图的显示,从而在主视图出现之前,飞溅不会变得可见。 dispatchDraw()方法绘制了splash视图的所有子视图,因此通过覆盖它并在触发事件之前调用super.dispatchDraw(),我们可以确保在启动主视图之前绘制子视图。 - Carl
+1为 giving the user immediate access to your app, that is your very best option. 完全同意(作为工程师可能?) - Attacktive


它不那么难;你只需创建一个你将用作启动画面的视图(一个布局简单且不需要大量测量的视图),使用setContentView将其设置为活动的内容;

然后使用复杂的布局再次调用活动上的setContentView,这需要一段时间来构建。在使用复杂布局第二次调用setContent之前,甚至可以使用Asynctask来加载数据。这取决于您是否受到数据加载或视图构建的约束。


2
2018-06-29 11:17



如果调用setContentView()以显示启动视图,然后立即再次为主视图调用,则在第二个视图显示之前,启动视图将无法显示。现在,如果你有一个工作线程在显示启动后做了很多东西,并且有 那 线程发布到UI处理程序以执行最终的setContentView(),这应该工作,只要异步的东西需要一段时间。但是如果你的延迟来自主视图本身(每个slipbull的注释),那么你需要等到启动绘制才触发第二个setContentView()。 - Carl


public class MyLocationListener extends Activity {

    public Handler myHandler = new Handler(){
             public void handlerMessage(Message msg){
                  set you contentView here after splash screen
             }
          } 

    public MyLocationListener(Context context){
        this.context = context;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                 setContentView(R.layout.splashscreen);
                // don't set Content View here instead start a thread here to do the task yiou want to do. 
                // Now post message from new thread to UI Thread using handlers.

         }

}

1
2018-06-29 11:24





制作启动画面的最佳方法是:

  1. 如果用户按下,则需要快速进入主屏幕。
  2. 没有动画。

我到达了这个很好的解决方案:

import android.app.Activity; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.view.WindowManager; import br.eti.fml.android.sigame.R;

import java.util.concurrent.atomic.AtomicBoolean;

public class LauncherActivity extends Activity {
    private AsyncTask goingToNextScreen;
    private AtomicBoolean alreadyShown = new AtomicBoolean(false);

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.launcher);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        //noinspection unchecked
        goingToNextScreen = new AsyncTask() {

            @Override
            protected Object doInBackground(Object... objects) {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    // ignores
                }

                return null;
            }

            @Override
            protected void onPostExecute(Object o) {
                goNext();
            }
        }.execute();
    }

    @Override
    public void onBackPressed() {
        if (goingToNextScreen != null) {
            goingToNextScreen.cancel(true);

            goNext();
        }
    }

    private void goNext() {
        if (alreadyShown.compareAndSet(false, true)) {
            startActivity(new Intent(LauncherActivity.this, HomeActivity.class));
            overridePendingTransition(0, 0);
            finish();
            overridePendingTransition(0, 0);
        }
    } }

1
2018-04-29 21:39





有时,使用splash,应用程序需要几毫秒或秒来加载Activity的内容。

如果你只想要像往常一样的“backgorund图像”启动画面。我认为最好的方法是使用主题。

以SherlockActionBar为例:

<style name="SplashTheme" parent="Theme.Sherlock.NoActionBar">
    ...
    <item name="android:windowBackground">@drawable/splash</item>
    ...
</style>

splash可以是.9文件来填充屏幕。

并且清单中的活动必须是这样的

    <activity
        android:name=".SplashActivity"
        ...
        android:theme="@style/SplashTheme"
        ...>
        ...
     </activity>

那么你的代码中不需要setContent(View)行。并且主题将比内容加载更快。

这允许您从应用程序加载开始时拥有启动画面。没有黑色的窗户或动作栏或类似的东西。


1
2017-11-08 15:09





// Splash代码的代码

public class SplashScreen extends Activity {

    static int SPLASH_TIMEOUT = 5000;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash_layout);

    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            startActivity(new Intent(SplashScreen.this, MainActivity.class));
            finish();
        }
    }, SPLASH_TIMEOUT);
    }
}

这里 SPLASH_TIMEOUT 将定义您自己的活动应该如何显示,所以根据您的需要更改此值。

// MainActivity.class的代码

public class MainActivity extends ActionBarActivity {

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

    }

}

-3
2017-07-25 08:56



OP表示他不想要定时器或延迟。阅读问题。 - stealthjong