Android文字跑马灯

1. 继承TextView自定义控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.anjoyo.musicplayer.define;  
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.TextView;

public class MarqueeTextView extends TextView {

public MarqueeTextView(Context context) {
super(context);
// android:ellipsize="marquee"
// android:focusable="true"
// android:marqueeRepeatLimit="marquee_forever"
// android:focusableInTouchMode="true"
// android:scrollHorizontally="true"
//// android:singleLine="true"
// setSingleLine(true);
// setEllipsize(TruncateAt.MARQUEE);
// setFocusable(true);
// setMarqueeRepeatLimit(Integer.MAX_VALUE);
// setFocusableInTouchMode(true);
}

public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}

public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}

@Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
}

@Override
public boolean isFocused() {
return true;
}

}

2. 在布局中的使用

1
2
3
4
5
6
7
8
9
10
<com.anjoyo.musicplayer.define.MarqueeTextView  
android:id="@+id/tv_folder_filepath"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:gravity="clip_vertical"
style="@style/MarqueeTextView"
android:textColor="#ffffff"
android:textSize="12sp"
android:layout_below="@+id/tv_folder_name"
/>

3. style.xml

1
2
3
4
5
6
7
8
<style name="MarqueeTextView">  
<span style="white-space:pre"> </span><item name="android:ellipsize">marquee</item>
<item name="android:focusable">true</item>
<item name="android:marqueeRepeatLimit">marquee_forever</item>
<item name="android:focusableInTouchMode">true</item>
<item name="android:scrollHorizontally">true</item>
<item name="android:singleLine">true</item>
</style>

Android实现歌词滑动显示

继承TextView自定义LyricView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import java.util.List;  

import com.anjoyo.musicplayer.bean.LyricBean;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.widget.TextView;

public class LyricView extends TextView {

/** 控件的高 */
private float mHeight;
/** 控件的宽 */
private float mWidth;
private Paint mPaint;
private Paint prePaint;
private Paint nextPaint;

/** 当前歌曲的歌词 */
private List<LyricBean> lyricBeanList;
/** 歌词进度,List中的位置 */
private int index;

private float heightSign;
private boolean start;

private Shader inShader = new LinearGradient(0, 0, 0, LYRIC_ONE_HEIGHT,
Color.argb(0xcc, 0xcc, 0xcc, 0xcc),
Color.argb(0xff, 0xff, 0xff, 0xff), TileMode.MIRROR);
private Shader outShader = new LinearGradient(0, LYRIC_ONE_HEIGHT, 0, 0,
Color.argb(0xcc, 0xcc, 0xcc, 0xcc),
Color.argb(0xff, 0xff, 0xff, 0xff), TileMode.MIRROR);

/** 歌词显示的字体大小 */
private static final int LYRIC_TEXT_SIZE = 36;
/** 每行歌词的高度 */
private static final int LYRIC_ONE_HEIGHT = 60;

public LyricView(Context context) {
super(context);
initView();
}

public LyricView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}

public LyricView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();

new UpdateThread().start();
}

private void initView() {
setFocusable(true);

mPaint = new Paint();
mPaint.setAntiAlias(true);// 设置画笔的锯齿效果
mPaint.setTextAlign(Align.CENTER);
mPaint.setTextSize(LYRIC_TEXT_SIZE);
mPaint.setTypeface(Typeface.SERIF);

prePaint = new Paint(mPaint);
prePaint.setShader(outShader);
nextPaint = new Paint(mPaint);
nextPaint.setShader(inShader);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (canvas == null) {
return;
}
mPaint.setColor(Color.argb(0xff, 0xff, 0xff, 0xff));//当前歌词词句颜色
if (lyricBeanList == null || lyricBeanList.size() < 1 || index > lyricBeanList.size() - 1) {
canvas.drawText("好音质,MusicPlayer!", mWidth / 2, mHeight / 2, mPaint);
return;
}
canvas.drawText(lyricBeanList.get(index).getLyric(), mWidth / 2, heightSign, mPaint);

mPaint.setColor(Color.argb(0xcc, 0xcc, 0xcc, 0xcc));//非当前歌词词句颜色

float currentY = heightSign;
//画出本句之前的歌词
for (int i = index - 1; i >= 0; i--) {
currentY -= LYRIC_ONE_HEIGHT;
if (i == index - 1) {
canvas.drawText(lyricBeanList.get(i).getLyric(), mWidth / 2,
currentY, prePaint);
} else {
canvas.drawText(lyricBeanList.get(i).getLyric(), mWidth / 2,
currentY, mPaint);
}
}
currentY = heightSign;
//画出本句之后的歌词
for (int i = index + 1; i < lyricBeanList.size(); i++) {
currentY += LYRIC_ONE_HEIGHT;
if (i == index + 1) {
canvas.drawText(lyricBeanList.get(i).getLyric(), mWidth / 2,
currentY, nextPaint);
} else {
canvas.drawText(lyricBeanList.get(i).getLyric(), mWidth / 2,
currentY, mPaint);
}
}
}

public void setLyricList(List<LyricBean> lyricBeanList) {
this.lyricBeanList = lyricBeanList;
}

public void setStart(boolean start) {
this.start = start;
}

public boolean getStart() {
return this.start;
}

public void setIndex(int index) {
if (this.index != index) {
this.index = index;
heightSign = mHeight / 2 + LYRIC_ONE_HEIGHT / 2;
}
}

private class UpdateThread extends Thread {
@Override
public void run() {
while (true) {
if (start && lyricBeanList.size() > index + 1) {
if (heightSign > mHeight / 2f - LYRIC_ONE_HEIGHT / 2f) {
heightSign -= LYRIC_ONE_HEIGHT /
(float)(lyricBeanList.get(index + 1).getLyricTime()
- lyricBeanList.get(index).getLyricTime()) * 50;
}
postInvalidate();
}
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
heightSign = h / 2f + LYRIC_ONE_HEIGHT / 2f;
}


}

LyricBean.java如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.List;  

/**
* 一句歌词
*
* @author HLP
*/
public class LyricBean {

/** 一句歌词 */
private String lyric;
/** 本句歌词开始的时间 */
private int lyricTime;
/** 本句歌词每个字的持续时间 ,构成数组 */
private List<Integer> wordsTime;

public LyricBean(String lyric, int lyricTime, List<Integer> wordsTime) {
super();
this.lyric = lyric;
this.lyricTime = lyricTime;
this.wordsTime = wordsTime;
}

public String getLyric() {
return lyric;
}

public void setLyric(String lyric) {
this.lyric = lyric;
}

public int getLyricTime() {
return lyricTime;
}

public void setLyricTime(int lyricTime) {
this.lyricTime = lyricTime;
}

public List<Integer> getWordsTime() {
return wordsTime;
}

public void setWordsTime(List<Integer> wordsTime) {
this.wordsTime = wordsTime;
}

}

有个模拟【天天动听】的项目在GitHub上,需要手机中有MP3歌曲和天天动听格式的歌词,
歌曲和歌词文件名一致,查看请点击

Android的五中数据存储方式

1. SharedPreference:通过键值对形式保存简单的私有数据

类似于Java中的Properties类
保存的也是key = value,是以xml文档格式保存。

1
2
3
4
5
6
7
8
9
10
11
12
//获得SharedPreferences的实例  
shared = getSharedPreferences(文件名不需要后缀,文件的创建模式);
//文件的创建模式
Context.MODE_APPEDN(追加)
Context.MODE_PRIVATE(私有)
Context.MODE_WORLD_READABLE(公有可读)
Context.MODE_WORLD_WRITEABLE(公有可写)
//获得可以修改的对象Editor
editor = share.edit();
editor.putXXX(key, value);
//提交执行,写入文档
editor.commit();

查看文件
-> window -> preference -> showView -> file Explorer

路径:/data/data/[your package name]/shared_prefs/

1
2
3
4
5
6
7
8
9
10
//得到写入而能当的内容  
shared.getXXX(key, 默认值);
shared.getAll();//返回的是一个Map<String, ?>
StringBuffer sb = new StringBuffer();
sb.append("name = ")
.append(shared.getString("name", null)
.append("\r\n")
.append("password = ")
.append(shared.getString("password", null);
editText.setText(sb);

2. 文件存储:以文件的形式存储数据,内部存储——保存私有数据,外部存储——保存公有数据

(1)内部存储

1
2
3
4
5
6
7
8
os = openFileOutput(文件名,文件创建模式同上);  
is = opFileInput(文件名);
//文件保存的位置:/data/data/your package mame/files/
//获取路径
File dir = Context.getFilesDir();
File file = dir + “//” + FILE_NAME;
//读取数据时应该判断文件是否存在。
//这两种方式都是要占据项目的空间,使得系统运行时会出现空间不足的问题。

(2)外部(SD卡)存储

1
2
3
4
5
//先判断SDCard是否存在file explorer ./mnt/sdcard/  
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
//获得SDCard的根目录
String filePath = Environment.getExternalStorageDirectory() + File.separator + "b103" + File.separator + "test.txt";
}

配置文件中加权限

1
2
<uses-permission  android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

其他操作与普通的JAVA的IO操作没有区别。

3. SQLite数据库:把结构化的数据保存在一个私有的数据库中

4. 网络存储:把数据保存在网络上开发者自己的服务器中

5. XML存储:通过XML文件存储数据

也相当于文件存储的一种形式。

Android TabHost的三中实现方式

  • Android
  • TabHost
1. 继承TabActivity实现,此方法最为简单,但灵活性、实用性不高

此时的setup可以省略不写

2. 继承Activity自定义TabHost,自己写布局(此方法最为常用)

layout.xml

<TabHost >
    <span style="white-space:pre">    </span><TabWidget ></TabWidget>
    <span style="white-space:pre">    </span><FrameLayout >
    <span style="white-space:pre">        </span><LinearLayout ></LinearLayout>
    <span style="white-space:pre">        </span><LinearLayout ></LinearLayout>
        ...
    <span style="white-space:pre">    </span></FrameLayout>
</TabHost>

Activity.class

TabHost tabHost = (TabHost)findViewByID();  
tabHost.setup();      //此时的setup不可省略不写
3. 继承ActivityGroup,结合RadioButton(和FrameLayout)实现TabHost

需有mTabHost.setup(getLocalActivityManager());

Android自定义Dialog 软键盘、大小的设置

Dialog中EditText可弹出软键盘

1
2
3
4
5
6
7
8
9
10
<style name="CustomDialogStyle" parent="@android:style/Theme.Dialog">  
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:backgroundDimAmount">0.6</item>
</style>

把style设置到dialog中

1
2
3
4
5
Dialog ad = new Dialog(context,R.style.CustomDialogStyle);  
ad.show();
Window window = ad.getWindow();
window.setBackgroundDrawable(new ColorDrawable(0));
window.setContentView(R.layout.cancel_sos_dialog);

Dialog中设置大小

代码承接上面的代码

1
2
3
4
WindowManager.LayoutParams  lp= window.getAttributes();    
lp.width=screenWidth - 50;//定义宽度
lp.height=LayoutParams.WRAP_CONTENT;//定义高度
window.setAttributes(lp);

Android动态加载View的几种方法

  • Android
  • 加载View

有以下加载方式

  1. View view = LayoutInflater.from(context).inflate(要装载的界面的id, 根节点);//context为this,根节点一般为null
    view.findViewByID();//与inflate配套使用的。
    setContentView();//配套使用findViewByID(动态界面中的控件的id);

  2. LayoutInflater inflater = ((Activity)context).getLayoutInflater();

  1. 另一种方法获得
    LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVER);
    view = inflater.inflater (resource, root);

  2. view = View.inflate(context, R.layout.header, null);

Sencha自定义ImageField用于表单中显示图片

苦于sencha源码中

xtype Class
textfield Ext.field.Text
numberfield Ext.field.Number
textareafield Ext.field.TextArea
hiddenfield Ext.field.Hidden
radiofield Ext.field.Radio
filefield Ext.field.File
checkboxfield Ext.field.Checkbox
selectfield Ext.field.Select
togglefield Ext.field.Toggle
fieldset Ext.form.FieldSet

并没有ImageField,显示图片起来非常麻烦,于是查看源码自定义了一个.

secnha-touch-defineBySelf,js该文件在secha原库和程序入口之间加载即可

1
2
3
<script src="js/sencha-touch-all-2.3.0.js"></script>  
<script src="js/sencha-touch-defineBySelf.js"></script>
<script src="app.js"></script>

secnha-touch-defineBySelf,js内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**  
* @Class Ext.Img.ImageField
* @createTime 2014-04-03
* 用于表单中的ImageField,通过setValue()来设置图片路径显示图片,通过getValue()来获得图片的路径
* form.getValues()时也会在values中存入图片的相应路径字段
* 用法:
* 具有一般的Panel的所有属性,并且还多一个一个value属性可以设置图片的路径
*/
Ext.define('Ext.field.ImageField', {
extend : 'Ext.Panel',
xtype : ['imagefield', 'imgfield'],

isField: true,
isFormField: true,

config : {
name : null,
value : null,
items: [
{
xtype : 'img',
id : 'self-img_photo',
width : '100%',
height : '100%',
margin : '0 0 0 0',
style : '{background-repeat:no-repeat; background-size:100% auto; background-position:center;}',
},
],
},

setValue : function(path) {
Ext.getCmp('self-img_photo').setSrc(path);
},

getValue : function() {
return Ext.getCmp('self-img_photo').getSrc();
},

});

Hybrid开发 Sencha pinch事件的监听实现图片的缩放功能

为实现跨平台开发

使用Sencha写了一个程序,监听img的pinch事件;

封装了一个ImageView,实现了图片的缩放功能

注意:ImgPinch是我项目的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/** 
* 可以图片缩放的ImageView
*/
Ext.define('ImgPinch.view.ImageView', {
extend : 'Ext.Panel',
xtype : 'imageView',
config : {
style:"background:black",
src : null,
items : [
{
xtype : "img",
id : "_show_image",
width : "100%",
height : "100%",
margin : "0 0 0 0",
cls : "showImageCls",

},
],
},

initialize: function() {
var me = this;
me.callParent();

//设置图片的路径
var view = imageView = Ext.getCmp("_show_image");
imageView.setSrc(me.getSrc());

//记录双指触屏的位置touch0(x0, y0), touch1(x1, y1), 中心点(centerX0, centerY0)==============
var x0, y0, x1, y1, touch0, touch1, centerX0, centerY0;
x0 = y0 = x1 = y1 = centerX0 = centerY0 = -1;

//新获取的双指触屏的位置touch00(x00, y00), touch11(x11, y11), 中心点(centerX1, centerY1)===================
var x00, y00, x11, y11, touch00, touch11, centerX1, centerY1;

//ImageView可视宽高===================================================
var viewVisionWidth, viewVisionHeight;
viewVisionWidth = me.element.dom.clientWidth;
viewVisionHeight = me.element.dom.clientHeight;

//ImageView的宽高===================================================
var viewWidth, viewHeight;
viewWidth = imageView.element.dom.clientWidth;
viewHeight = imageView.element.dom.clientHeight;
//ImageView的上边距和左边距===================================================
var top, left, topMin, leftMin, topMax, leftMax;
topMin = viewVisionHeight - viewHeight;
leftMin = viewVisionWidth - viewWidth;
topMax = leftMax = 0;

view.element.on('pinchstart', function(event, node, options, eOpts) {
touch0 = event.touches[0];
x0 = touch0.pageX;
y0 = touch0.pageY;
touch1 = event.touches[1];
x1 = touch1.pageX;
y1 = touch1.pageY;

//两个触摸点的中间点初始化
centerX0 = (x0 + x1) / 2;
centerY0 = (y0 + y1) / 2;
}, view);

view.element.on('pinch', function(event, node, options, eOpts) {
touch00 = event.touches[0];
x00 = touch00.pageX;
y00 = touch00.pageY;
touch11 = event.touches[1];
x11 = touch11.pageX;
y11 = touch11.pageY;

if(x0 == -1 || y0 == -1) {
return;
}
//两个触摸点的差值
var minusX = Math.abs(x11-x00) - Math.abs(x1-x0);
var minusY = Math.abs(y11-y00) - Math.abs(y1-y0);
//两个触摸点的中间点初始化
centerX1 = (x00 + x11) / 2;
centerY1 = (y00 + y11) / 2;

var oldWidth = view.getWidth();
var oldHeight = view.getHeight();
console.log("pinch 进行缩放oldWidth is " + oldWidth + ", oldHeight is " + oldHeight + "");
var addWidth = parseInt(oldWidth.split("%")[0])+minusX;
var addHeight = parseInt(oldHeight.split("%")[0])+minusY;
console.log("pinch 进行缩放addWidth is " + addWidth + "%, addHeight is " + addHeight + "%");
//缩放比值
var zoomScaling = addWidth;
var minusZoom = minusX;
if(Math.abs(minusX) < Math.abs(minusY) && addHeight >= 100 && addHeight < 800) {
//放大取大值,缩小取值,绝对值大取绝对值大的
zoomScaling = addHeight;
minusZoom = minusY;
}
//控制缩放范围在0%~500%之间
if(zoomScaling >= 100 && zoomScaling < 800) {
view.setWidth(zoomScaling + "%");
view.setHeight(zoomScaling + "%");
//保证图片一致处于中央位置
var marginArr = view.getMargin().split(" ");
top = - viewVisionHeight * minusZoom / 200 + parseInt(marginArr[0]);
left = - viewVisionWidth * minusZoom / 200 + parseInt(marginArr[3]);

//两指中心点有改变,修正值
if(centerX0 != -1 && centerY0 != -1) {
top += centerY1 - centerY0;
left += centerX1 - centerX0;
//重置数据
centerX0 = centerX1;
centerY0 = centerY1;
}
if(top < topMin) {
top = topMin;
} else if (top > topMax) {
top = topMax;
}
if(left < leftMin) {
left = leftMin;
} else if (left > topMax) {
left = leftMax;
}
view.setMargin(top + " 0 0 " + left);
//重置一些数据
viewWidth = imageView.element.dom.clientWidth;
viewHeight = imageView.element.dom.clientHeight;
console.log("wh ImageView:(" + viewWidth + ", " + viewHeight + ")");
topMin = viewVisionHeight - viewHeight;
leftMin = viewVisionWidth - viewWidth;
x0 = x00;
y0 = y00;
x1 = x11;
y1 = y11;
}

}, view);

view.element.on('pinchend', function(event, node, options, eOpts) {
x0 = y0 = x1 = y1 = -1;
}, view);

//移动ImageView记录touchstart时的位置(x2, y2)========================
var x2, y2;
x2 = y2 = -1;
view.element.on('touchstart', function(event, node, options, eOpts) {
viewVisionWidth = me.element.dom.clientWidth;
viewVisionHeight = me.element.dom.clientHeight;
x2 = event.pageX;
y2 = event.pageY;
}, view);
view.element.on('touchmove', function(event, node, options, eOpts) {
var touches = event.touches;
switch(touches.length) {
case 1:
if(viewWidth > viewVisionWidth) {
console.log("touch Move==>位置:(" + event.pageX + ", " + event.pageY + ")");
var marginArr = view.getMargin().split(" ");
console.log("touch Move==>Margin初始值:" + marginArr.toString());
top = event.pageY - y2 + parseInt(marginArr[0]);
left = event.pageX - x2 + parseInt(marginArr[3]);
if(top < topMin) {
top = topMin;
} else if (top > topMax) {
top = topMax;
}
if(left < leftMin) {
left = leftMin;
} else if (left > topMax) {
left = leftMax;
}
console.log("touch Move==>Margin变换值:(" + top + " 0 0 " + left + ")");
view.setMargin(top + " 0 0 " + left);
}
x2 = event.pageX;
y2 = event.pageY;
break;
case 2:

break;
default:
break;
};
}, view);
view.element.on('touchend', function(event, node, options, eOpts) {
x2 = y2 = -1;
}, view);
},
});

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var view = Ext.create("Ext.Panel", {  
layout : "vbox",
items : [
{
xtype : "button",
height : 60,
text : "点击直接网页浏览",
handler : function() {
//可打开手机中任意图片(也可为本程序中相对路径的图片)
// uri = "file:///storage/sdcard0/DCIM/Camera/IMG_20140329_135428.jpg";
// window.open(uri,"image/jpeg");
window.open("images/test2.jpg","image/jpeg");
//下面的方法只能打开本程序中的图片
// window.location = "file:images/test1.jpg";
}
},
{
xtype : "imageView",
flex : 1,
src : "images/test1.jpg",
},
],
});
Ext.Viewport.setActiveItem(view);

sencha list实现多选

  • hybrid
  • sencha
  • listview
  • checkbox

dataview和listview中有配置字段“mode”,对于该字段的说明:

Modes of selection. Valid values are ‘SINGLE’, ‘SIMPLE’, and ‘MULTI’. Defaults to: ‘SINGLE’. Available since: 2.0.0

MULTI就是多选
此外还提供了各种‘%select%’相关的方法,在API中搜索即可看到。

windows mosquitto.exe闪退

  • mosquitto
  • mqtt
  • 推送

解决方法:

计算机–>管理–>服务——>

找到Mosquitto Broker,若为启动状态,点击“停止”,然后右键属性设置为手动启动。

若还是不行,则1883端口被占用了。

命令行查询:netstat -ano | findstr 1883

杀掉进程的命令:taskkill /pid 4 /f
其中 /f 表示强制关闭该进程,4是查询结果显示的pid号