Action View layout

Action View

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler

在這一章中我們將進入MVC架構中的View,也就是提供介面給用戶操作,與我們的應用程式做互動。

ActionViewRails中處理View的元件名稱,而提供給用戶的文件,我們會用Template樣板來呈現。本章假設讀者們都對HTML有基本的認識。

Template樣板

什麼是Template樣板呢? 我們知道伺服器最終提供給瀏覽器的格式是HTML文件,而Template樣板就是動態產生HTML的方式。

相對的說,我們用靜態HTML來稱呼不經過程式產生的HTM 文件

Rails預設用來產生Template的方式是Embedded Ruby(ERb),如果你曾經使用過PHPJSPASP,那麼你會非常熟悉這種內嵌程式碼的風格,這是一種最為直覺且容易學習的方法。例如以下是一小段嵌入目前時間的ERb,中間<%= %>的部份便是Ruby程式:

<h1><%= Time.now.to_s %></h1>

RailsTemplate檔案位置和名稱也是有玄機的,例如app/views/welcome/index.html.erb來說,welcome目錄是它的Controller名稱,檔案第一段index是它的Action名稱,附檔名則是用來指定要用什麼方式來產生什麼格式的文件:index.html.erb表示用ERb產生HTML格式的文件。會有這樣慣例的原因,你可能已經猜到,那就是使用ERb不代表一定就是用來產生HTML。用什麼Template引擎(在Rails中又叫作Template Handler)產生文件,和文件的Format格式是兩回事情。所以ERb其實可以用來產生任何文字檔格式,例如CSVXMLJavaScript等等。

雖然可以,但ERb並不是產生XML的最好方式,通常在我們會用Builder來產生XML,例如一個叫做show.xml.builder的檔案:

people do |p|
  p.person "test"
end

就會產生以下的XML

<people>
  <person>test<person>
</people>

以下是常見的樣板引擎與格式組合:

格式 引擎
html、xhtml erb
js erb
xml、rss、atom builder

擴充Template Handler

Rails預設只有內建ERbBuilder這兩套樣板引擎,但要擴充非常容易。例如在Rails社群中,也很流行用HAML這套樣板引擎來取代ERbHAML是一套利用縮排技術簡化HTML撰寫的格式,例如:

#content
  .box
    test

就會產生以下的HTML

<div id="content">
  <div class="box">
    test
  </div>
</div>

要安裝使用,只需要在Gemfile檔案中加上gem "haml-rails"然後bundle install即可。不過相較於ERb,使用HAML雖然可以更為有效率地撰寫HTML樣板,但是會需要考量團隊中的網頁設計師是否能夠配合使用。

使用RendererController中直接回傳結果

有一些格式的本質其實不需要Template引擎,只需要再Controller中直接render其結果即可,例如JSONCSV或是XMLRailsActiveRecord model提供了to_xmlto_json方法。而CSV則可以使用FasterCSV函式庫。範例如下:

require 'csv'
class PeopleController < ApplicationController

def index
    @people = Person.all
    respond_to do |format|
      format.html
      format.json{ render :json => @person.to_json }
      format.xml { render :xml => @person.to_xml }
      format.csv do
        csv_string = CSV.generate do |csv|
            csv << ["Name", "Created At"]
            @people.each do |person|
                csv << [person.name, person.created_at]
            end
        end
        render :text => csv_string
      end
    end
end

Ruby 1.8.7則需要在Gemfile中指定gem "fastercsv",將CSV改成用FasterCSV

ERb標籤

除了上述介紹的ERb標籤<%= %>會輸出中間的Ruby程式執行結果,還有一些其他用法:

<% %>

這樣就不會輸出任何結果,通常用在if或迴圈條件中,例如:

<% @people.each do |person| %>
    <% if person.name.present? %>
        <p><%= person.name %></p>
    <% end %>
<% end %>

上述的<% %>標籤雖然不會輸出HTML內容,但是還是在HTML原始碼中換行了,為了避免輸出時多餘的換行,可以改用<%- -%>。不過實際上並沒有很多人在乎就是了,畢竟這不影響用戶的頁面。

<%# blah blah %>

這是註解,不會輸出任何內容。不過如果需要整段多行註解,會常這樣寫:

<%- output = false -%>
<% if output %>
    <%= foo %>
    <hr>
    <%= bar %>
<% end %>

這時候只要控制output的值就是輸出的開關,稍後我們可以將output變成一個Helper方法,這樣就可以動態決定內容的輸出與否了。

Layout版型

Layout可以用來包裹Template樣板,讓不同View可以共用Layout作為文件的頭尾。因此我們可以為全站的頁面建立共用的版型。這個檔案預設是app/views/layouts/application.html.erb。如果在app/views/layouts目錄下有跟某Controller同名的Layout檔案,那這個Controller下的所有Views就會使用這個同名的Layout

預設的Layout長得如下:

<!DOCTYPE html>
<html>
<head>
  <title>YourApplicationName</title>
  <%= stylesheet_link_tag    "application" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

其中的<%= yield %>會被替換成個別Action的樣板。

開頭的<!DOCTYPE html>說明了這是一份HTML5文件,這種宣告法向下相容於所有瀏覽器的HTML4

如果需要指定ControllerLayout,可以這麼做:

class EventsController < ApplicationController
   layout "special"
end

這樣就會指定Events Controller下的Views都使用app/views/layouts/special.html.erb這個Layout,你可以加上參數:only:except表示只有特定的Action

class EventsController < ApplicationController
   layout "special", :only => :index
end

或是

class EventsController < ApplicationController
   layout "special", :except => [:show, :edit, :new]
end

請注意到使用字串和Symbol是不同的。使用Symbol的話,它會透過一個同名的方法來動態決定,例如以下的Layout是透過determine_layout這個方法來決定:

class EventsController < ApplicationController
   layout :determine_layout

	private

	def determine_layout
   	   ( rand(100)%2 == 0 )? "event_open" : "event_closed"
	end
end

除了在Controller層級設定Layout,我們也可以設定個別的Action使用不同的Layout,例如:

def show
   @event = Event.find(params[:id])
	render :layout => "foobar"
end

這樣show Action的樣板就會套用foobar Layout。更常見的情形是關掉Layout,這時候我們可以寫render :layout => false

自定Layout內容

除了<%= yield %>會載入Template內容之外,我們也可以預先自定一些其他的區塊讓Template可以置入內容。例如,要在Layout中放一個側欄用的區塊,取名叫做:sidebar

<div id="sidebar">
    <%= yield :sidebar %>
</div>
<div id="content">
    <%= yield %>
</div>

那麼在Template樣板中,任意地方放:

<%= content_for :sidebar do %>
   <ul>
       <li>foo</li>
       <li>bar</li>
   </ul>
<% end %>

那麼這段內容就會被置入到Layout<%= yield :sidebar %>之中。

Layout版型的繼承(進階)

局部樣板Partials

局部樣板可以將Template中重複的程式碼抽出來,例如我們在Part1中示範過的新增和編輯的表單。Partial Template的命名慣例是底線開頭,但是呼叫時不需加上底線,例如:

<%= render :partial => "common/nav" %>

這樣便會使用app/views/common/_nav.html.erb這個樣板。如果使用Partial的樣板和Partial所在的目錄相同,可以省略第一段的common路徑。

Partial樣板中是可以直接使用實例變數的(也就是@開頭的變數)。不過好的實務作法是透過:locals明確傳遞區域變數,這樣程式會比較清楚,Partial樣板也比較容易被重複使用:

<%= render :partial => "common/nav", :locals => { :a => 1, :b => 2 } %>

這樣在partial樣板中,就可以存取到區域變數ab

如果是陣列的資料,一般直覺會這樣寫:

@items.each do |item|
  render :partial => "item", :locals => { :item => item }
end

但是,partial可以支援處理陣列形式:

render :partial => "item", :collection => @items, :as => :item

這樣的好處不只是少打字而已,還有效能上的好處,Rails會針對這種形式做最佳化。

集合型Collection

像是trli這類會一直重複的Template元素,我們可以使用collection參數來處理,Rails有針對集合型的Partial做效率最佳化,例如像以下的程式:

<ul>
    <% @people.each do |person| %>
        <%= render :partial => "person", :locals => { :person => person } %>
    <% end %>
<ul>

可以改使用Collection方式,

<ul>
    <%= render :partial => "person", :collection => @people %>
<ul>

如此既可以少掉兩行each程式碼,執行的效能又更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现 BottomNavigationView 中间凸起的效果,可以使用第三方库或者自定义控件实现。以下是自定义控件的实现方式: 1. 创建一个自定义控件类,继承自 BottomNavigationView。 2. 在自定义控件类的构造函数中,调用 init() 方法初始化控件。 3. 在 init() 方法中,设置 BottomNavigationView 的 ItemIconTintList、ItemTextColor 和 ItemBackgroundResource 属性,以及添加中间凸起的 Item。 4. 在自定义控件类中,重写 onMeasure() 方法,使中间凸起的 Item 占据 BottomNavigationView 的一定比例。 5. 在自定义控件类中,重写 onDraw() 方法,绘制中间凸起的 Item。 6. 在布局文件中使用自定义控件。 具体实现细节可以参考以下代码示例: ```java public class CustomBottomNavigationView extends BottomNavigationView { private static final int ITEM_SIZE = 5; // BottomNavigationView 的 Item 数量 private static final int MIDDLE_ITEM_INDEX = 2; // 中间凸起的 Item 索引 private static final int MIDDLE_ITEM_SIZE = 80; // 中间凸起的 Item 大小 private Paint mPaint; private Path mPath; public CustomBottomNavigationView(Context context) { super(context); init(); } public CustomBottomNavigationView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CustomBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { // 设置 BottomNavigationView 的 ItemIconTintList、ItemTextColor 和 ItemBackgroundResource 属性 int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; int[] colors = new int[]{ Color.GRAY, Color.BLUE, }; ColorStateList csl = new ColorStateList(states, colors); setItemIconTintList(csl); setItemTextColor(csl); setBackgroundResource(R.color.white); // 添加中间凸起的 Item Menu menu = getMenu(); menu.add(Menu.NONE, R.id.item_middle, Menu.NONE, "") .setIcon(R.drawable.ic_add) .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); // 初始化画笔和路径 mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getResources().getColor(R.color.white)); mPath = new Path(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 计算中间凸起的 Item 大小 int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int itemWidth = width / ITEM_SIZE; int middleItemWidth = MIDDLE_ITEM_SIZE * (ITEM_SIZE - 2) / ITEM_SIZE; int middleItemHeight = MIDDLE_ITEM_SIZE / 2; // 设置中间凸起的 Item 大小 MenuItem middleItem = getMenu().findItem(R.id.item_middle); middleItem.setIcon(R.drawable.ic_add); middleItem.setWidth(middleItemWidth); middleItem.setHeight(middleItemHeight); // 设置中间凸起的 Item 占据 BottomNavigationView 的一定比例 ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.height = height + middleItemHeight; setLayoutParams(layoutParams); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制中间凸起的 Item float centerX = getWidth() / ITEM_SIZE * MIDDLE_ITEM_INDEX; float centerY = getHeight() / 2; float radius = MIDDLE_ITEM_SIZE / 2; mPath.reset(); mPath.moveTo(centerX - radius, centerY - radius); mPath.lineTo(centerX + radius, centerY - radius); mPath.lineTo(centerX, centerY + radius); mPath.close(); canvas.drawPath(mPath, mPaint); } } ``` 使用自定义控件时,只需在布局文件中引用即可: ```xml <com.example.CustomBottomNavigationView android:id="@+id/bottom_navigation_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/bottom_navigation_menu" /> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值