私は過去のプロジェクトでこれと非常によく似たことをしました:
バックグラウンド
Arhimed が彼の回答で述べたように、また BlackBerry フォーラムで読むことができるようField
に、ListField
. 行のコンテンツは、テキストやビットマップなどとしてListField
直接描画されるだけです。コンテンツはインスタンスではないため、フォーカスできません。drawListRow()
Field
それで、私がしたことListField
は、のサブクラスに置き換えることでしたManager
。もともと、私は を使用してVerticalFieldManager
いましたが、それで問題が発生しました。また、スタック オーバーフローに関する多くの問題も見てきました。そこでは、人々が をサブクラス化VerticalFieldManager
し、1 つの小さな動作だけをカスタマイズすると、すべてが壊れ始めます。VerticalFieldManager
通常の動作を受け入れるとうまく機能するように思えます。さらに何かが必要な場合は、Manager
直接拡張するだけです。垂直に積み重ねられた行のレイアウトを実行するのは非常に簡単です。
次に、各行を独自Manager
に作成し、カスタム レイアウトを実装しsublayout()
て、行の を必要な場所に配置しField
ました。次に、行をフォーカス可能にし、行のビットマップ/ボタンを個別にフォーカス可能にすることもできます(星のように)。行をクリックすると 1 つのアクションが呼び出され、星をクリックすると別のアクションが呼び出されます。
ただし、私のアプリでは、10 ~ 20 行しかないため、パフォーマンスは問題ではありませんでした。また、あなたの例に合わせてコードを変更する必要があったため、このコードは軽くテストしただけだと考えてください。ただし、私はそれをアプリに組み込んだので、私の仮定とあなたの説明が有効である限り、問題なく動作するはずです.
実装
まず、あなたが何であるかがわかりませんでしたListRander
(そのコードを示していませんでした)。ただし、私のコードでは、1 つの行に関する詳細を含むデータ クラスが必要です。それがあなたが使用した方法のように見えたListRander
ので、それが私が使用したものです:
public class ListRander {
private String _title;
private Bitmap _thumb;
public ListRander(String title, Bitmap thumb) {
_title = title;
_thumb = thumb;
}
public String getTitle() {
return _title;
}
public Bitmap getThumb() {
return _thumb;
}
}
CustomListField
次に、あなたのクラスを自分のクラスに置き換えました。
public class CustomListField extends Manager implements FocusChangeListener {
private int _MAX_ROW_HEIGHT = 60;
private boolean _searchable = false;
private Vector _listData;
private FieldChangeListener _fieldListener;
public CustomListField(Vector data) {
super(FOCUSABLE | VERTICAL_SCROLL | VERTICAL_SCROLLBAR);
setSearchable(true);
setEditable(false);
setListData(data);
}
public void setChangeListener(FieldChangeListener listener) {
// we need to save this listener, because we set it to listen to all new rows
_fieldListener = listener;
int numFields = getFieldCount();
for (int f = 0; f < numFields; f++) {
getField(f).setChangeListener(listener);
}
super.setChangeListener(listener);
}
public int getRowHeight() {
return _MAX_ROW_HEIGHT;
}
public void setSearchable(boolean searchable) {
_searchable = searchable;
}
public int getSelectedIndex() {
return getFieldWithFocusIndex(); // TODO??
}
public Object get(int index) {
return _listData.elementAt(index);
}
public int indexOfList(String prefix, int start) {
if (start >= _listData.size() || !_searchable) {
return -1;
} else {
int result = getSelectedIndex(); // the default result if we find no matches
for (Enumeration e = _listData.elements(); e.hasMoreElements(); ) {
String rowString = (String) e.nextElement();
if (rowString.startsWith(prefix)) {
return _listData.indexOf(rowString);
}
}
return result;
}
}
protected boolean navigationClick(int status, int time) {
CustomListRow focus = (CustomListRow) getFieldWithFocus();
if (focus != null) {
// see if the row wants to process this click
if (!focus.navigationClick(status, time)) {
// let our FieldChangeListener know that this row has been clicked
fieldChangeNotify(getFieldWithFocusIndex());
}
return true;
} else {
return false;
}
}
protected void sublayout(int width, int height) {
int w = Math.min(width, getPreferredWidth());
int h = Math.min(height, getPreferredHeight());
int rowHeight = getRowHeight();
int numRows = getFieldCount();
setExtent(w, h);
setVirtualExtent(w, rowHeight * numRows);
for (int i = 0; i < numRows; i++) {
Field f = getField(i);
setPositionChild(f, 0, rowHeight * i);
layoutChild(f, w, rowHeight);
}
}
public int getPreferredWidth() {
return Display.getWidth();
}
public int getPreferredHeight() {
return Display.getHeight();
}
public void setListData(Vector listData) {
_listData = listData;
if (listData != null) {
int listSize = listData.size();
int numRows = getFieldCount();
for (int s = 0; s < listSize; s++) {
if (s < numRows) {
// we can reuse existing CustomListRows
CustomListRow row = (CustomListRow) getField(s);
row.setData((ListRander) listData.elementAt(s));
} else {
CustomListRow row = new CustomListRow((ListRander) listData.elementAt(s));
row.setChangeListener(_fieldListener);
row.setFocusListener(this);
add(row);
}
}
if (listSize < numRows) {
// delete the excess rows
deleteRange(listSize, numRows - listSize);
}
} else {
deleteAll();
}
invalidate();
}
public void focusChanged(Field field, int eventType) {
// we handle scrolling here, when focus changes between rows
if (eventType == FOCUS_GAINED) {
if (field.getTop() < getVerticalScroll()) {
// field is off the top of the screen, so scroll up
setVerticalScroll(field.getTop());
} else if (field.getTop() >= getVerticalScroll() + getVisibleHeight()) {
// field is off the bottom of the screen, so scroll down
setVerticalScroll(field.getTop() - getVisibleHeight() + getRowHeight());
}
}
}
}
CustomListRow
最後に、1 つの行が私のクラスによって表されます。
public class CustomListRow extends Manager implements FieldChangeListener {
private static final int _MAX_ROW_HEIGHT = 60;
private ListRander _data;
private BitmapField _thumb;
private LabelField _title;
private FocusableBitmapField _star;
private static final Bitmap _starImg = Bitmap.getBitmapResource("star.png");
private static final Bitmap _bgImg = Bitmap.getBitmapResource("listing_bg.png");
private SeparatorField _separator;
private int _fontColor = Color.BLACK;
private boolean _highlighted = false;
private int _width;
// subclass exists to expose focus methods (make public)
private class FocusableBitmapField extends BitmapField {
public FocusableBitmapField() {
super(_starImg, BitmapField.FOCUSABLE | BitmapField.EDITABLE);
}
public void onFocus(int direction) {
super.onFocus(direction);
}
public void onUnfocus() {
super.onUnfocus();
}
}
public CustomListRow(ListRander data) {
super(Field.FOCUSABLE | Manager.NO_VERTICAL_SCROLL | Manager.NO_VERTICAL_SCROLLBAR);
setBackground(BackgroundFactory.createBitmapBackground(_bgImg));
_width = Display.getWidth();
long labelStyle = (DrawStyle.LEFT | DrawStyle.TOP | DrawStyle.ELLIPSIS);
_title = new LabelField("", labelStyle) { // custom anonymous class to change font color
protected void paint(Graphics g) {
int c = g.getColor();
g.setColor(_fontColor);
super.paint(g);
g.setColor(c);
}
};
_title.setFont(Font.getDefault().getFontFamily().getFont(Font.PLAIN, 24));
_thumb = new BitmapField();
_star = new FocusableBitmapField();
_star.setChangeListener(this);
_separator = new SeparatorField() { // custom anonymous class to change separator color
protected void paint(Graphics g) {
int c = g.getColor();
g.setColor(Color.GRAY);
super.paint(g);
g.setColor(c);
}
};
setData(data);
add(_thumb);
add(_title);
add(_star);
add(_separator);
}
public ListRander getData() {
return _data;
}
public void setData(ListRander value) {
if (value != _data) {
_data = value;
_title.setText(value.getTitle());
_thumb.setBitmap(value.getThumb());
}
}
private void onStarClicked() {
Dialog.alert("Star has been clicked or tapped!");
}
private void onRowClicked() {
Dialog.alert("Row has been clicked or tapped!");
}
public void fieldChanged(Field field, int context) {
if (field == _star) {
onStarClicked();
}
}
public boolean navigationClick(int status, int time) {
if (_star.isFocus()) {
onStarClicked();
return true;
} /* else {
onRowClicked();
return true;
} */
return false; // we will not consume this event
}
protected void highlight(boolean onRow) {
_fontColor = onRow ? Color.WHITE : Color.BLACK; // change font color for contrast
_highlighted = onRow;
invalidate();
}
protected void onFocus(int direction) {
// called when focus first transfers to this row, from another Field
if (direction == 1) {
// coming from top to bottom, we highlight the row first, not the star
highlight(true);
} else if (direction == -1) {
// coming from bottom to top, we highlight the star button first, not the row
_star.onFocus(direction);
highlight(false);
}
}
protected void onUnfocus() {
// remove highlighting of the row, if any
highlight(false);
super.onUnfocus();
}
protected int moveFocus(int amount, int status, int time) {
// called when this row already has focus (either on row, or star button)
if (amount > 0) {
// moving top to bottom
if (!_star.isFocus()) {
// we were on the row, now move to the star button
_star.onFocus(1);
highlight(false);
amount--; // consume one unit of movement
}
} else {
// moving from bottom to top
if (_star.isFocus()) {
// we were on the star button, now move back over to the row
_star.onUnfocus();
highlight(true);
amount++; // consume one unit of movement
}
}
return amount;
}
protected boolean touchEvent(net.rim.device.api.ui.TouchEvent event) {
// We take action when the user completes a click (a.k.a. unclick)
int eventCode = event.getEvent();
if ((eventCode == TouchEvent.UNCLICK) || (eventCode == TouchEvent.DOWN)) {
// Get the touch location, within this Manager
int x = event.getX(1);
int y = event.getY(1);
if ((x >= 0) && (y >= 0) && (x < _width) && (y < _MAX_ROW_HEIGHT)) {
int field = getFieldAtLocation(x, y);
if ((field >= 0) && (getField(field) == _star)) {
// Let event propagate to (star) button field
return super.touchEvent(event);
} else {
if (eventCode == TouchEvent.UNCLICK) {
// A completed click anywhere else in this row should popup details for this selection
fieldChangeNotify(1);
onRowClicked();
} else {
// This is just a soft touch (TouchEvent.DOWN), without full click
setFocus();
}
// Consume the event
return true;
}
}
}
// Event wasn't for us, let superclass handle in default manner
return super.touchEvent(event);
}
protected void sublayout(int width, int height) {
height = Math.min(getPreferredHeight(), height);
setExtent(_width, height);
final int margin = 5;
int thumbWidth = _thumb.getPreferredWidth();
layoutChild(_thumb, thumbWidth, _thumb.getPreferredHeight());
setPositionChild(_thumb, margin, margin);
int starWidth = _star.getPreferredWidth();
int starHeight = _star.getPreferredHeight();
layoutChild(_star, starWidth, starHeight);
setPositionChild(_star, width - starWidth - margin, (height - starHeight) / 2);
// this assumes you want margin between all fields, and edges
layoutChild(_title, width - thumbWidth - starWidth - 4 * margin, _title.getPreferredHeight());
setPositionChild(_title, margin + thumbWidth /* + margin */, margin); // TODO?
}
protected void paintBackground(Graphics g) {
super.paintBackground(g);
if (_highlighted) {
// you can't override drawFocus() for a Manager, so we'll handle that here:
int oldColor = g.getColor();
int oldAlpha = g.getGlobalAlpha();
XYRect rect = new XYRect();
g.setGlobalAlpha(150);
g.setColor(Color.BLUE);
getFocusRect(rect);
drawHighlightRegion(g, HIGHLIGHT_FOCUS, true, rect.x, rect.y, rect.width, rect.height);
g.setGlobalAlpha(oldAlpha);
g.setColor(oldColor);
}
}
public int getPreferredWidth() {
return _width;
}
public int getPreferredHeight() {
return _MAX_ROW_HEIGHT;
}
}
使用法
これは、リスト フィールド全体を使用する方法です (おそらくScreen
クラス内で)。
public class ListScreen extends MainScreen implements FieldChangeListener {
public ListScreen() {
try {
Vector data = new Vector();
Bitmap icon = Bitmap.getBitmapResource("list_icon.png");
for (int i = 0; i < 15; i++) {
ListRander lr = new ListRander("Product Name " + i, icon);
data.addElement(lr);
}
CustomListField list = new CustomListField(data);
add(list);
list.setChangeListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public void fieldChanged(Field field, int context) {
if (field instanceof CustomListRow) {
CustomListRow row = (CustomListRow) field;
Dialog.alert(row.getData().getTitle() + " was selected!");
}
}
}
CustomListRow
私のアプリでは、それ自体がスターのクリックに相当するものを処理することは理にかなっています。しかし、私にとっては、行のクリックをそのように処理することは意味がありませんでした。そのため、任意の行が選択されたときにコールバックされる a自体を設定できます。上記の画面クラスの例を参照してください。クラス内でも行クリックを処理したい場合は、それで問題ありません。そこで方法を打ち出しました。コード内でコメントアウトされている場所を検索すると、そのメソッドを実装して再アクティブ化できます ( )。FieldChangeListener
CustomListField
CustomListRow
onRowClicked()
onRowClicked()
問題
- 私のアプリはリスト検索を必要としませんでした。私はそのサンプル実装をレイアウトしました
ListField
。しかし、私はそれをテストしませんでした。必要に応じて、それがあなたの仕事です。実装を開始しましたCustomListField
(「参考文献」を参照indexOfList()
)。
- あなたの「ナビゲーションバー」の目的がわかりませんでした。バーは通常、ステータス バーやツールバーのような全幅のアイテムです。あなたのスクリーンショットにはそのようなものは見当たりません。ナビゲーションアイテムは、各行の右側にある小さな矢印で、詳細を表示します。しかし、あなたのスクリーンショットでもそれはわかりませんでした。だから、私はそのコードを無視しました。ナビゲーション バーが必要な場合は、それがどうあるべきかを明らかに知っているので、それを上記のコードに追加できます。
- 行の背景画像の一部として星を追加しただけなのか、それとも別の画像を用意したのかわかりませんでした。星を表す別のstar.pngを追加しました。星をクリックすると、それが塗りつぶされたり、強調表示されたりすると思います。しかし、あなたはその問題を説明していないので、それを処理できると思います。星を表すカスタム フィールドが必要な場合は、選択した画像と選択していない画像を含めることができます。それを新しい質問として投稿してください。
- 行の幅を行の高さの 3 倍に設定しようとしているように見えるコードがいくつかありましたが、それはスクリーン ショットと一致しませんでした。とにかく、ほとんどのリストは全画面幅です。だから、私はそのコードを削除します。私
CustomListRow
のクラスgetPreferredWidth()
は全画面幅を実装して要求します。お好みで変更してください。