• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

scaloid: Scaloid is a library that simplifies your Android code. It makes your c ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

scaloid

开源软件地址:

https://gitee.com/scalalibs/scaloid

开源软件介绍:

Simpler Android

Scaloid is a library that simplifies your Android code. It makes your code easy to understand and maintain by leveraging Scala language.

For example, the code block shown below:

val button = new Button(context)button.setText("Greet")button.setOnClickListener(new OnClickListener() {  def onClick(v: View) {    Toast.makeText(context, "Hello!", Toast.LENGTH_SHORT).show()  }})layout.addView(button)

is reduced to:

SButton("Greet", toast("Hello!"))

Benefits

Demos

Fork one of this to start a new project:

Learn how Scaloid can be used in action:

Contents

Other links

Core design principle

"Being practically simple" is number one principle of Scaloid. Most frequently used things should be written shorter, like Huffman coding. To do this, I first observed Android programs I wrote, and thought that which part of the code is more fundamental than others. For example, what is the most essential part of buttons? Buttons should have some visible things on it, such as title or image, so the buttons are created like this: SButton("Hello"). The second essential part is doing something when it is pressed: SImageButton(R.drawable.hello, toast("World!")). What should be the third one? The answer might not the same for every people, but I think that repetition frequency of press-and-hold action is nice: SButton("Add", n += 1, 500) increases n for every 500 milliseconds when the user holds the button.

UI Layout without XML

Android SDK leverages XML to build UI layouts. However, XML is considered still a bit verbose, and lacks programmability. Scaloid composes UI layout in Scala DSL style, therefore achieve both clarity and programmability. For example, suppose a legacy XML layout as shown below:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:orientation="vertical" android:layout_width="match_parent"        android:layout_height="wrap_content" android:padding="20dip">    <TextView android:layout_width="match_parent"            android:layout_height="wrap_content" android:text="Sign in"            android:layout_marginBottom="25dip" android:textSize="24.5sp"/>    <TextView android:layout_width="match_parent"            android:layout_height="wrap_content" android:text="ID"/>    <EditText android:layout_width="match_parent"            android:layout_height="wrap_content" android:id="@+id/userId"/>    <TextView android:layout_width="match_parent"            android:layout_height="wrap_content" android:text="Password"/>    <EditText android:layout_width="match_parent"            android:layout_height="wrap_content" android:id="@+id/password"            android:inputType="textPassword"/>    <Button android:layout_width="match_parent"            android:layout_height="wrap_content" android:id="@+id/signin"            android:text="Sign in"/>    <LinearLayout android:orientation="horizontal"            android:layout_width="wrap_content"            android:layout_height="wrap_content">        <Button android:text="Help" android:id="@+id/help"                android:layout_width="match_parent" android:layout_height="wrap_content"/>        <Button android:text="Sign up" android:id="@+id/signup"                android:layout_width="match_parent" android:layout_height="wrap_content"/>    </LinearLayout></LinearLayout>

is reduced to:

new SVerticalLayout {  STextView("Sign in").textSize(24.5.sp).<<.marginBottom(25.dip).>>  STextView("ID")  SEditText()  STextView("Password")  SEditText() inputType TEXT_PASSWORD  SButton("Sign in")  new SLinearLayout {    SButton("Help")    SButton("Sign up")  }.wrap.here}.padding(20.dip)

The layout description shown above is highly programmable. You can easily wire your logic into the layout:

new SVerticalLayout {  STextView("Sign in").textSize(24.5.sp).<<.marginBottom(25.dip).>>  STextView("ID")  val userId = SEditText()  STextView("Password")  val pass = SEditText() inputType TEXT_PASSWORD  SButton("Sign in", signin(userId.text, pass.text))  new SLinearLayout {    SButton("Help", openUri("http://help.url"))    SButton("Sign up", openUri("http://signup.uri"))  }.wrap.here}.padding(20.dip)

Because a Scaloid layout description is plain Scala code, it is type-safe.

Automatic layout converter

This converter turns an Android XML layout into a Scaloid layout:

http://layout.scaloid.org

Migration tip

Scaloid is fully compatible with legacy xml layout files.You can access a widget described in xml layout as:

onCreate {  setContentView(R.layout.main)  val name = find[EditText](R.id.name)  // do something with `name`}

Responsive layout

Basically, a layout written in Scaloid is just an ordinary Scala code, so you can just freely composite the layout according to the device configuration:

import org.scaloid.util.Configuration._if(long) SButton("This button is shown only for a long screen "  + "dimension ("+ width + ", " + height + ")")if(landscape) new SLinearLayout {  SButton("Buttons for")  SButton("landscape layout")  if(dpi <= HDPI) SButton("You have a high resolution display!")}.here

Please refer to this blog post for more detail:

Further readings about Scaloid layout

Lifecycle management

With Android API, Registering and unregistering BroadcastReceiver can be done as:

var connectivityListener: BroadcastReceiver = nulldef onResume() {  super.onResume()  // ...  connectivityListener = new BroadcastReceiver {    def onReceive(context: Context, intent: Intent) {     doSomething()    }  }   registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))}def onPause() {  unregisterReceiver(connectivityListener)  // ...  super.onPause()}

In Scaloid, the directly equivalent code is:

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {  doSomething()}

Scaloid has highly flexible resource register/unregister management architecture.If this code is written in services, registering and unregistering is done in onCreate and onDestroy respectively.If the same code is in activities, registering and unregistering is done in onResume and onPause respectively.This is just a default behavior.You can override a preference that determine when the register/unregister preforms.Overriding it is simple as well:

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {  doSomething()}(this, onStartStop)

Then, the receiver is registered onStart, and unregistered onStop.

onDestroy can be called in many times!

You can declare onDestroy behaviors in many places. This simplifies resource management significantly. Suppose you want to open a stream from a file:

def openFileStream(file: File): InputStream = {  val stream = new FileInputStream(file)  onDestroy(stream.close()) // automatically closed when the Activity is destroyed!!  stream}

onDestroy is a method that adds a function into the job list triggered when the activity is destroyed. So, we just get a stream from openFileStream() and forget about releasing it.Other lifecycle states (onCreate, onResume, onStop and so on) can be treated in the same way.

Further reading: Refer to this blog post for more details.

Asynchronous task processing

Android API provides runOnUiThread() only for class Activity. Scaloid provides a Scala version of runOnUiThread() for anywhere other than Activity.

Instead of:

activity.runOnUiThread {  new Runnable() {    def run() {      debug("Running only in Activity class")    }  }}

In Scaloid, use it like this:

runOnUiThread(debug("Running in any context"))

Running a job asynchronously and notifying the UI thread is a very frequently used pattern. Although Android API provides a helper class AsyncTask, implementing such a simple idea is still painful, even when we use Scala:

new AsyncTask[String, Void, String] {  def doInBackground(params: Array[String]) = {    doAJobTakeSomeTime(params)  }  override def onPostExecute(result: String) {    alert("Done!", result)  }}.execute("param")

Using scala.concurrent.Future, the asynchronous job shown above can be rewritten like this:

Future {  val result = doAJobTakeSomeTime(params)  runOnUiThread(alert("Done!", result))}

When you don't want to build sophisticate UI interactions, but just want to display something by calling a single Scaloid method (e.g. alert, toast, and spinnerDialog), Scaloid handles runOnUiThread for you. Therefore, the code block shown above is reduced to:

Future {  alert("Done!", doAJobTakeSomeTime(params))}

It is a great win as it exposes your idea clearly.

Just like we thrown away AsyncTask, we can also eliminate all other Java helpers for asynchronous job, such as AsyncQueryHandler and AsyncTaskLoader. Compare with the original Java codeand a Scala port of ApiDemos example app.

Using Future is just an example of asynchronous task processing in Scaloid. You can freely use any modern task management utilities.

Further reading: Refer to this blog post for an important consideration when using Future in Android.

Implicit conversions

Scaloid employs several implicit conversions. Some of the available implicit conversions are shown below:

Uri conversion
String => Uri

The functions such as play ringtones play() or open URIs openUri() takes an instance of Uri as a parameter. However, we frequently have URIs as a String. Scaloid implicitly converts String into Uri. Therefore, you can freely use String when you play a ringtone:

play("content://media/internal/audio/media/50")

, open a URI:

openUri("http://scaloid.org")

, or wherever you want.

Alternatively, you can specify the conversion as:

val uri:Uri = "http://scaloid.org".toUri
Unit conversion

Units dip and sp can be converted into the pixel unit.

val inPixel:Int = 32.dipval inPixel2:Int = 22.sp

Reversely, pixel unit can also be converted into dip and sp unit.

val inDip:Double = 35.px2dipval inSp:Double = 27.px2sp
Resource IDs

Scaloid provides several implicit conversions that convert from Int type resource ID to CharSequence, Array[CharSequence], Array[String], Drawable and Movie.For example:

def toast(msg:CharSequence) = ...toast(R.string.my_message) // implicit conversion works!

Although Scaloid provides these conversions implicitly, explicit conversion may be required in some context. In this case, methods r2... are provided for the Int type:

warn("Will display the content of the resource: " + R.string.my_message.r2String)

Currently, r2Text, r2TextArray, r2String, r2StringArray, r2Drawable and r2Movie is provided.

Further reading:

Context as an implicit parameter

Many methods in the Android API require an instance of a class Context. Providing this for every method call results in clumsy code. We employ an implicit parameter to eliminate this. Just declare an implicit value that represents current context:

implicit val ctx = ...

or just extend trait SContext, which defines it for you. Then the code that required Context becomes much simpler, for example:

Intent
new Intent(context, classOf[MyActivity])

is reduced to:

SIntent[MyActivity]

When a method takes an Intent as a first parameter in which we want to pass the newly created intent object, the parameter can be omitted. For example:

startService(new Intent(context, classOf[MyService]))stopService(new Intent(context, classOf[MyService]))

is reduced to:

startService[MyService]stopService[MyService]

or

val intent = // initialize the intent and put some attributes on itintent.start[MyActivity]

An intent that has a long list of extra attributes:

new Intent().putExtra("valueA", valueA).putExtra("valueB", valueB).putExtra("valueC", valueC)

is reduced to:

new Intent().put(valueA, valueB, valueC)
Toast
toast("hi, there!")

If you want a longer toast:

longToast("long toast")
Dialog
ProgressDialog.show(context, "Dialog", "working...", true)

is reduced to:

spinnerDialog("Dialog", "working...")

When you call toast, longToast or spinnerDialog from non-UI thread, you don't have to mind about threading.The toast example shown above is equivalent to the following Java code:

activity.runOnUiThread(new Runnable() {    public void run() {        Toast.makeText(activity, "hi, there!", Toast.LENGTH_SHORT).show();    }});
Pending intent
PendingIntent.getActivity(context, 0, new Intent(context, classOf[MyActivity]), 0)PendingIntent.getService(context, 0, new Intent(context, classOf[MyService]), 0)

is reduced to:

pendingActivity[MyActivity]pendingService[MyService]
Open URIs

This opens a web browser (or another view assigned to the http protocol).

openUri("http://scaloid.org")
System services

Getting system service objects become much simpler. The following legacy code:

val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]vibrator.vibrate(500)

is reduced to:

vibrator.vibrate(500)

Under the hood, Scaloid defines a function vibrator like this:

def vibrator(implicit ctx: Context) = ctx.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]

All the system service accessors available in Android API level 8 are defined (e.g. audioManager, alarmManager, notificationManager, etc.). The name of a system service accessor is the same as its class name, except that the first character is lowercased.

Enriched Implicit classes

Suppose an Android class Foo, for example, Scaloid defines an implicit conversion Foo => RichFoo. The class RichFoo defines additional methods for more convenient access to Foo. This is a common pattern in Scala to extend existing API (see pimp-my-library pattern). This section describes various features added on existing Android API classes.

Listeners

Android API defines many listener interfaces for callback notifications. For example, View.OnClickListener is used to be notified when a view is clicked:

find[Button](R.id.search).setOnClickListener(new View.OnClickListener {  def onClick(v:View) {    openUri("http://scaloid.org")  }})

Scaloid provides a shortcut that dramatically reduces the length of the code:

find[Button](R.id.search).onClick(openUri("http://scaloid.org"))

All other listener-appending methods such as .onKey(), .onLongClick(), and .onTouch() are defined.

Some conventions we employed for method naming are:

  • We omit set..., add..., and ...Listener from the method name, which is less significant.
    For example, .setOnKeyListener() becomes .onKey().
  • Every method has two versions of parameters overridden. One is a lazy parameter, and another is a function which has full parameters defined in the original Android API. For example, these two usages are valid:
button.onClick(info("touched"))button.onClick((v:View) => info("touched a button "+v))
  • Methods add... is abbreviated with a method += if it is not a listener-appender.
    For example, layout.addView(button) becomes layout += button.
Multiple method listeners

Methods beforeTextChanged(), onTextChanged(), and afterTextChanged() are defined in RichTextView, which can be implicitly converted from TextView. It is more convenient than using TextWatcher directly. For example:

inputField.beforeTextChanged(saveTextStatus())

is equivalent to:

inputField.addTextChangedListener(new TextWatcher {  def beforeTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {    saveTextStatus()  }  def onTextChanged(p1: CharSequence, p2: Int, p3: Int, p4: Int) {}  def afterTextChanged(p1: Editable) {}})

Also, we override beforeTextChanged() with full parameters defined in the original listener:

inputField.beforeTextChanged((s:CharSequence, _:Int, _:Int) => saveText(s))

Other listeners in Android API can also be accessed in this way.

Layout context

In Android API, layout information is stored into a View object via the method View.setLayoutParams(ViewGroup.LayoutParams). A specific type of parameter passing into that method is determined by a the type of ...Layout object which contains the View object. For example, let us see some Java code shown below:

LinearLayout layout = new LinearLayout(context);Button button = new Button(context);button.setText("Click");LinearLayout.LayoutParams params = new LinearLayout.LayoutParams();params.weight = 1.0f;  // sets some valuebutton.setLayoutParams(params
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap