【发布时间】:2011-09-25 01:36:58
【问题描述】:
如何在 Android 中制作真正的启动画面?我不想要计时器或延迟。只是在您的应用程序加载之前显示的初始屏幕。
【问题讨论】:
标签: android splash-screen
如何在 Android 中制作真正的启动画面?我不想要计时器或延迟。只是在您的应用程序加载之前显示的初始屏幕。
【问题讨论】:
标签: android splash-screen
没那么难;您只需创建一个将用作启动画面的视图(具有简单布局且不需要大量测量的视图),使用 setContentView 将其设置为活动的内容;
然后使用需要一段时间构建的复杂布局再次在活动上调用 setContentView。您甚至可以在使用复杂布局第二次调用 setContent 之前使用 Asynctask 加载数据。这取决于您是否受到数据加载或视图构建的约束。
【讨论】:
类似
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();
}
【讨论】:
.execute(),一定是在复制粘贴时出错了:)
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.
}
}
【讨论】:
下面出现了一个带有代码的解决方案,过去六周以来,它似乎在许多测试设备上都运行良好。
但是,在进入完整的初始屏幕之前,应该考虑一些准备工作。
首先,如果您可以通过立即调出应用的主视图、让用户立即访问您的应用来避免使用启动画面,那么这是您的最佳选择。
您通常可以通过立即调出主显示器的图形来完成此操作,然后创建一个工作线程来执行任何耗时的初始化任务,例如加载应用程序始终使用的表格。
但是,您的主视图的图形可能本身需要很长时间才能设置和显示,并且您希望在此期间看到其他内容初始化。
现在,如果您的主要活动有一个简单(例如,默认)、浅色或黑色、不透明的背景,那么当用户启动您的应用程序。我个人发现用作原始“飞溅”显示的背景主题包括以下内容(将添加到清单文件中主要活动的活动标签中):
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,所以我们需要在 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 定义为半透明的黑色背景,这样会导致启动器显示变暗,文本“正在加载...”以绿色叠加在顶部。
剩下的就是在主要活动的 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) 的各种设备(包括手机和平板电脑)上测试了该代码。
上述方法的潜在责任是,在某些情况下,闪屏视图可能不发出已完成的信号,因此闪屏会“卡在”主显示,没有主视图可以替换它。这从来没有发生在我身上,但我想在这里向 cmets 征求关于上面 SplashView 中 dispatchDraw() 的覆盖是否可能不会被调用。我对触发 dispatchDraw() 的代码进行了视觉检查,在我看来,鉴于我在 SplashView 构造函数中所做的初始化,它似乎总是会被调用。
如果有人有更好的方法来实现同样的目的,我会很高兴听到它。令我惊讶的是,当视图完成显示时,我找不到任何专门为触发而定制的覆盖,因此,如果存在并且我不知何故错过了它,请在下面发表评论。也非常欢迎评论确认这种方法将奏效!
【讨论】:
giving the user immediate access to your app, that is your very best option. 完全同意(也许是工程师?)
制作启动画面的最佳方法是:
我找到了这个很好的解决方案:
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);
}
} }
【讨论】:
有时,使用启动,应用需要几毫秒或几秒的时间来加载 Activity 的内容。
如果您只需要像往常一样的初始屏幕“背景图像”。我认为最好的方法是使用主题。
以 SherlockActionBar 为例:
<style name="SplashTheme" parent="Theme.Sherlock.NoActionBar">
...
<item name="android:windowBackground">@drawable/splash</item>
...
</style>
其中 splash 可以是一个 .9 文件来填充屏幕。
Manifest 中的 Activity 必须类似于
<activity
android:name=".SplashActivity"
...
android:theme="@style/SplashTheme"
...>
...
</activity>
那么您的代码中不需要 setContent(View) 行。而且主题的加载速度会比内容快。
这使您可以在应用程序加载开始时有一个启动画面。没有黑色窗口或 actionBars 或类似的东西。
【讨论】:
//启动代码的代码
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);
}
}
【讨论】: