少量圖片對於我們賦值是沒有什么難度,但是如果圖片的量大的話,我們肯定希望能很直接地顯示在界面上供我們使用,再就是排放的位置等等,這些都需要比較直觀的操作,在實際應用中會讓我們省很多力以及時間。下面這個例子給出了解決的方法,當然大家有需要的話,可自行下載更改代碼,變成自己的項目。
本例地址: http://hightopo.com/guide/guide/core/listview/examples/example_custom.html
實現圖如下:

首先,創建場景,HT 中有一個 BorderPane 面板組件是拿來頁面排布的,可以排布 html 標簽,也可以排布 HT 的組件,這里我們將整個頁面分為三個部分,頂部工具條 toolbar、左側列表 listView 和中間 3d 場景 g3d,再將這個面板組件添加進 html body 體中:
borderPane = new ht.widget.BorderPane();//面板組件
toolbar = new ht.widget.Toolbar(); //工具條
listView = new ht.widget.ListView(); //列表組件
g3d = new ht.graph3d.Graph3dView();// 3d 組件
borderPane.setTopView(toolbar);//將 toolbar 放置到面板中的頂部 borderPane.setLeftView(listView, 350); //將 listView 放置到面板中的左側 borderPane.setCenterView(g3d); //將 g3d 放置到面板中的中間 borderPane.addToDOM(); //將面板組件添加進 body 中
addToDOM 函數是 HT 封裝好的將 HT 組件添加進 body 體中的一個方法,其實現邏輯如下:
addToDOM = function(){
var self = this, view = self.getView(),//通過 getView 函數獲取組件的底層 div style = view.style; document.body.appendChild(view); //body 添加孩子 view style.left = '0'; style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false);//窗口大小變化時,立即刷新組件 }
我們一個部分一個部分來解析,從最上層的 toolbar 工具條開始,如下:

工具條也是分為三個部分,一是左側的搜索框,二是中間的分割線,三是右側的點擊按鈕。
我們首先向工具條 toolbar 中添加這三個元素,具體添加方法請參考 HT for Web 工具條手冊:
toolbar.setItems([//設置工具條元素數組
{
id: 'text', label: 'Search', icon: 'images/search.png', textField: { width: 120 } }, 'separator', { label: 'Sort by price', type: 'toggle',//toggle表示開關按鈕 selected: true, action: function(){ listView.setSortFunc(this.selected ? sortFunc : null); } } ]);
接下來向左側的 listView 列表中添加數據,這個數據就是 product.js 中的變量 products,通過遍歷這個數組變量,將這個數組中的所有值都填充到 listView 列表中:

products.forEach(function(product){//products 是在product.js 文件中定義的
var data = new ht.Data();
data.a(product);//設置數據 data 的 attr 屬性
、listView.dm().add(data);//將 data 添加進 listView 的數據容器中
});
然后對 listView 列表進行一系列的樣式屬性的設置:行高、背景、icon 圖標、文字提示等等。代碼如下,解釋都在代碼中了,還有不懂的請查閱 HT for Web 列表手冊:
listView.setRowHeight(50);//設置行高
listView.drawRowBackground = function(g, data, selected, x, y, width, height){//繪制行背景色,默認僅在選中該行時填充選中背景色,可重載自定義
if(this.isSelected(data)){//選中時
g.fillStyle = '#87A6CB';
}
else if(this.getRowIndex(data) % 2 === 0){//偶數行時
g.fillStyle = '#F1F4F7'; } else{ g.fillStyle = '#FAFAFA'; } g.beginPath(); g.rect(x, y, width, height); g.fill(); }; // HT 通過 ht.Default.setImage('name', json) 函數來注冊圖片 ht.Default.setImage('productIcon', { width: 50, height: 50, clip: function(g, width, height) {//clip 用於裁剪繪制區域 //利用canvas畫筆繪制,實現自定義裁剪任意形狀的效果 //這里是將圖片裁剪成圓形 g.beginPath(); //x, y, radius, startAngle, endAngle, anticlockwise g.arc(width/2, height/2, Math.min(width, height)/2-3, 0, Math.PI * 2, true); g.clip(); }, comps: [//矢量圖形的組件Array數組,每個數組對象為一個獨立的組件類型,數組的順序為組件繪制先后順序 { type: 'image', stretch: 'uniform',//圖片始終保持原始寬高比例不變化,並盡量填充滿矩形區域 rect: [0, 0, 50, 50],//指定組件繪制在矢量中的矩形邊界 [x, y, width, height]四個參數方式,分別代表左上角坐標x和y,以及寬高width和height name: {func: function(data){return data.a('ProductId');}}//圖片的名字為 data.a('ProductId') 返回的值 } ] }); listView.setIndent(60);//設置indent縮進,該參數一般用於指定圖標的寬度 listView.getIcon = function(data){//返回data對象對應的icon圖標,可重載自定義 return 'productIcon';//這個是前面 ht.Default.setImage 函數注冊過的矢量圖形 }; listView.enableToolTip();//開啟文字提示 listView.getLabel = function(data){//返回data對象顯示的文字,默認返回data.toLabel(),可重載自定義 return data.a('ProductName') + ' - $' + data.a('UnitPrice').toFixed(2); }; listView.getToolTip = function(e){//根據傳入的交互事件,返回文本提示信息,可重載自定義 var data = this.getDataAt(e);//傳入邏輯坐標點或者交互event事件參數,返回當前點下的數據元素 if(data){ return '<span style="color:#3D97D0">ProductId: </span>' + data.a('ProductId') + '<br>' + '<span style="color:#3D97D0">ProductName: </span>' + data.a('ProductName') + '<br>' + '<span style="color:#3D97D0">QuantityPerUnit: </span>' + data.a('QuantityPerUnit') + '<br>' + '<span style="color:#3D97D0">Description: </span>' + data.a('Description'); } return null; };
列表組件中還封裝了一個很方便的函數 setSortFunc,用於設置排序函數,用戶也可以自定義,目前我們希望對這些”商品“進行排序:
sortFunc = function(d1, d2){//自定義排序函數
return d1.a('UnitPrice') - d2.a('UnitPrice');
};
listView.setSortFunc(sortFunc);//HT 定義的 設置排序函數
因為我們要進行數據的搜索,就要對數據以及顯示方面進行過濾,因為在數據變化時,HT 無法獲知需要更新,這時候就要我們手動對有顯示變化的部分調用更新函數 invalidate 簡寫為 iv。
我們對文本輸入框的鍵盤彈起事件進行事件的監聽,然后判斷我們輸入的值在 listView 列表中是否存在等操作對顯示界面進行過濾:
// 對text文本框進行鍵盤按鍵彈起事件監聽 toolbar.getItemById('text').element.getElement().onkeyup = function(e){
listView.invalidateModel();//無效模型,最徹底的刷新方式 “完全刷新”
};
//如果文本框輸入的值在
listView.setVisibleFunc(function(data){//設置可見過濾器
var text = toolbar.v('text');//getValue(id)根據id獲取對應item元素值,簡寫函數為v(id)
if(text){ return data.a('ProductName').toLowerCase().indexOf(text.toLowerCase()) >= 0;//indexOf()方法返回在類型數組中可以找到給定元素的第一個索引,如果不存在,則返回-1 } return true; });
第三個部分,右側 3d 場景,利用的是 HT 的三維組件 ht.graph3d.Graph3dView,然后在 3d 場景上添加兩個節點,作為對照:
//創建兩個節點放到 3d 場景中
var node = new ht.Node();
node.s3(30, 30, 30);//設置三維大小
node.p3(-30, 15, 0);//設置三維坐標
node.s('all.color', '#87A6CB');//設置 node 的六個面顏色
g3d.dm().add(node);//將新建的 node 添加進 3d 場景的數據容器中
var node = new ht.Node(); node.s3(30, 30, 30); node.p3(30, 15, 0); node.s('all.color', '#87A6CB'); node.setElevation(15); g3d.dm().add(node); g3d.setEye(-100, 100, 80);//設置 3d 場景的眼睛(或Camera)所在位置,默認值為[0, 300, 1000] g3d.setGridVisible(true);//設置是否顯示網格 g3d.setGridColor('#F1F4F7');//設置網格線顏色
整個場景創建完畢,接下來就是將 listView 中顯示的 icon 圖標拖拽到 3d 中的節點上,作為貼圖。列表組件中封裝了一個拖拽的功能 handleDragAndDrop,這個函數有兩個參數,event 交互事件和 state 當前狀態,我們對拖拽事件的不同狀態進行不同的處理:
listView.handleDragAndDrop = function(e, state){//該函數默認為空,若該函數被重載,則pan平移組件功能將被關閉
if(state === 'prepare'){//state當前狀態,先后會有prepare-begin-between-end四種過程
var data = listView.getDataAt(e);//傳入邏輯坐標點或者交互event事件參數,返回當前點下的數據元素
listView.sm().ss(data);//設置選中當前事件所在的數據元素
if(dragImage && dragImage.parentNode){
document.body.removeChild(dragImage);
}
dragImage = ht.Default.toCanvas('productIcon', 30, 30, 'uniform', data); // toCanvas(image, width, height, stretch, data, view, color)將圖片轉換成Canvas對象 productId = data.a('ProductId'); } else if(state === 'begin'){ if(dragImage){ var pagePoint = ht.Default.getPagePoint(e);//返回page屬性坐標 dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px';//實時更新拖拽時的圖標的位置 dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px'; document.body.appendChild(dragImage);//在 html body 體中添加這個拖拽的圖片 } } else if(state === 'between'){ if(dragImage){ var pagePoint = ht.Default.getPagePoint(e);//返回page屬性坐標 dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px'; dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px'; if(ht.Default.containedInView(e, g3d)){//判斷交互事件所處位置是否在View組件之上,一般用於Drog And Drop的拖拽操作判斷 //這邊做了兩個判斷,一個是鼠標在拖拽的時候未松開,一個是鼠標拖拽的時候松開了。 if(lastFaceInfo){//鼠標未松開的情況下,貼圖顯示舊值 //data.face 默認值為front,圖標在3D下的朝向,可取值left|right|top|bottom|front|back|center lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue); lastFaceInfo = null; } //鼠標松開時,將新值賦給這個面 var faceInfo = g3d.getHitFaceInfo(e);//獲取鼠標所在面信息 if(faceInfo){ faceInfo.oldValue = faceInfo.data.s(faceInfo.face + '.image');//獲取面的“老值” faceInfo.data.s(faceInfo.face + '.image', productId);//front/back/top/bottom/left/right.image 設置這些面的貼圖 lastFaceInfo = faceInfo; } } } } else{//拖拽結束之后,所有值都回到初始值 if(dragImage){//有從列表中拖拽圖片 if(lastFaceInfo){//有賦“圖片”到 3d 中的節點上 lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue); lastFaceInfo = null; } if(ht.Default.containedInView(e, g3d)){ var faceInfo = g3d.getHitFaceInfo(e); if(faceInfo){ faceInfo.data.s(faceInfo.face + '.image', productId); } } if(dragImage.parentNode){ document.body.removeChild(dragImage); } dragImage = null; productId = null; } } };
