package com.android.music.lrc;
import com.android.music.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemLongClickListener;
import com.android.music.lrc.widget.TextView;
import java.util.List;
/**
* LyricListView
*
* @author lisc
*/
public class LyricListView extends ListView implements OnItemLongClickListener {
private static final String TAG = "LyricListView";
private int colorNormal;
private int colorHighlight;
private String path;
private LRC lrc;
private int lyricListItem;
private int listHeight;
private int itemHeight;
private int marginHeight;
private boolean isScrolling;
private int delay;
private int offset;
private int curLrc;
private int lyricCount;
private int mFieldId;
private boolean initialized;
private LyricsAdapter mAdapter;
private boolean isLyricModified;
private LRC.PositionProvider posProvider;
/**
* LyricListView
*
* @param context
*/
public LyricListView(Context context) {
this(context, null);
}
/**
* LyricListView
*
* @param context
* @param attrs
*/
public LyricListView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
/**
* LyricListView
*
* @param context
* @param attrs
* @param defStyle
*/
public LyricListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ViewGroup);
listHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.View_minHeight, 0);
marginHeight = listHeight / 2;
a.recycle();
init();
}
private void init() {
offset = LyricConstants.DEFAULT_OFFSET;
delay = LyricConstants.DEFAULT_DELAY;
isScrolling = false;
curLrc = -1;
lyricCount = 0;
mFieldId = R.id.content;
initialized = false;
isLyricModified = false;
colorNormal = getResources().getColor(R.color.lyric_color_normal);
colorHighlight = getResources().getColor(R.color.lyric_color_highlight);
// setOnItemLongClickListener(this);
this.setSelector(android.R.color.transparent);
}
@Override
protected void layoutChildren() {
super.layoutChildren();
if (!initialized && lyricCount > 0) {
initialized = true;
// modified by housz ======begin===bug-1030:resolve
// NullPointerException========
if (getChildAt(1) != null) {
itemHeight = getChildAt(1).getHeight();
}
// modified by housz ======end===bug-1030:resolve
// NullPointerException========
}
}
private boolean checkRequirments() {
if (lrc == null) {
// throw new
// IllegalStateException("LRC object is null, you should invoke setLrc() first.");
Log.i(TAG, "LRC object is null, you should invoke setLrc() first.");
return false;
}
if (posProvider == null) {
// throw new
// IllegalStateException("PositionProvider object is null, you should invoke setPositionProvider() first.");
Log.i(TAG,
"PositionProvider object is null, you should invoke setPositionProvider() first.");
return false;
}
return true;
}
/**
* reset
*/
public void reset() {
curLrc = -1;
initialized = false;
}
/**
* start
*/
public void start() {
resume();
}
/**
* resume
*/
public void resume() {
if (checkRequirments()) {
Log.d(TAG, "isScrolling="+isScrolling);
if (!isScrolling) {
isScrolling = true;
reset();
lyricHandler.sendEmptyMessage(LyricConstants.LYRIC_SCROLL);
}
}
}
/**
* pause
*/
public void pause() {
if (isScrolling) {
isScrolling = false;
lyricHandler.removeMessages(LyricConstants.LYRIC_SCROLL);
}
}
/**
* stop
*/
public void stop() {
if (isScrolling) {
isScrolling = false;
lyricHandler.removeMessages(LyricConstants.LYRIC_SCROLL);
removeAllViewsInLayout();
lrc = null;
}
}
private int computeOffset() {
// ++curLrc;
curLrc = 0;
long pos = posProvider.getPosition();
List<LRC.Offset> ofs = lrc.getOffsets();
while (curLrc < lyricCount && pos > ofs.get(curLrc).time) {
curLrc++;
}
int off = 0;
long start = 0;
long end = 0;
if (curLrc >= lyricCount) {
off = lyricCount * itemHeight;
} else {
start = ofs.get(curLrc).time;
if (pos < start && curLrc > 0) {
start = lrc.getOffsets().get(--curLrc).time;
} else if (curLrc <= 0) {
return 0;
}
end = curLrc == lyricCount - 1 ? posProvider.getDuration() : lrc.getOffsets().get(
curLrc + 1).time;
off = (int) (1.0 * itemHeight / (end - start) * (pos - start));
off += curLrc * itemHeight;
}
int result = offset - off;
offset = off;
return result;
}
private Handler lyricHandler = new Handler() {
public void handleMessage(Message msg) {
int what = msg.what;
switch (what) {
case LyricConstants.LYRIC_SCROLL:
if (isScrolling) {
int off = computeOffset();
offsetChildrenTopAndBottom(off);
invalidateViews();
sendEmptyMessageDelayed(LyricConstants.LYRIC_SCROLL, delay);
}
break;
case LyricConstants.LYRIC_AJUST:
int off = computeOffset();
offsetChildrenTopAndBottom(off);
invalidateViews();
break;
default:
break;
}
}
};
/**
*
* @return boolean
*/
public boolean isLyricModified() {
return isLyricModified;
}
/**
*
* @return String
*/
public String getLrcPath() {
return path;
}
/**
* getLrc
* @return LRC
*/
public LRC getLrc() {
return lrc;
}
/**
* setLrc
*/
public void setLrc(String path, LRC lrc) {
setLrc(path, lrc, R.layout.lyric_list_item);
}
/**
* setLrc
*/
public void setLrc(String path, LRC lrc, int item) {
if (lrc == null){
throw new IllegalArgumentException("LRC object can not be null.");
}
stop();
this.path = path;
this.lrc = lrc;
this.lyricListItem = item;
this.lyricCount = lrc.getOffsets().size();
populateViews();
}
private void populateViews() {
String[] lyrics = lrc.listLyrics();
int len = lyrics.length;
String[] newLyrics = new String[len + 2];
newLyrics[0] = newLyrics[len + 1] = "";
System.arraycopy(lyrics, 0, newLyrics, 1, len);
if (mAdapter == null) {
mAdapter = new LyricsAdapter(getContext(), lyricListItem, newLyrics);
setAdapter(mAdapter);
} else {
mAdapter.setLyrics(newLyrics);
}
/*
* The content of the adapter has changed but ListView did not receive a notification.
* so we call notifyDataSetChanged to send the notification.
*/
mAdapter.notifyDataSetChanged();
}
/**
* ajustLyrics
*/
public void ajustLyrics(int row, boolean after, boolean advance, int time) {
isLyricModified = true;
lrc.ajust(row, after, advance, time);
populateViews();
lyricHandler.sendEmptyMessage(LyricConstants.LYRIC_AJUST);
}
/**
* setPositionProvider
*/
public void setPositionProvider(LRC.PositionProvider provider) {
Log.d(TAG, "setPositionProvider");
posProvider = provider;
}
/**
* onItemLongClick
* @return boolean
*/
public boolean onItemLongClick(AdapterView<?> parent, View view, final int pos, long id) {
// new AlertDialog.Builder(getContext()).setTitle(R.string.lyrics_edit_title)
// .setItems(R.array.lyrics_edit_item, new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// switch (which) {
// case 0: // current lyric advance 0.5 second
// ajustLyrics(pos - 1, false, true, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 1: // current lyric delayed 0.5 second
// ajustLyrics(pos - 1, false, false, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 2: // the after lyrics advance 0.5 second
// ajustLyrics(pos - 1, true, true, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 3: // the after lyrics delayed 0.5 second
// ajustLyrics(pos - 1, true, false, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 4: // all lyrics advance 0.5 second
// ajustLyrics(-1, false, true, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 5: // all lyrics delayed 0.5 second
// ajustLyrics(-1, false, false, LyricConstants.DEFAULT_AJUST_TIME);
// break;
// case 6:
// break; // do nothing
// default:
// if (isLyricModified) {
// boolean ret = LRCParser.saveToFile(getLrcPath(), getLrc());
//// Toast.makeText(
//// getContext(),
//// ret ? R.string.lyrics_update_success
//// : R.string.lyrics_update_fail,
//// Toast.LENGTH_SHORT).show();
// isLyricModified = !ret;
// } else {
//// Toast.makeText(getContext(), R.string.lyrics_no_update,
//// Toast.LENGTH_SHORT).show();
// }
// }
// }
// }).show();
///*
// * the call back function has dealt with the long click event ,so it returns true.
// */
return true;
}
/**
* getOffset
* @return int
*/
public int getOffset() {
return offset;
}
/**
* setOffset
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* getDelay
* @return int
*/
public int getDelay() {
return delay;
}
/**
* setDelay
*/
public void setDelay(int delay) {
this.delay = delay;
}
private class LyricsAdapter extends BaseAdapter {
private Context cxt;
private int resId;
private String[] lyrics;
private LayoutInflater inflater;
LyricsAdapter(Context c, int r, String[] l) {
cxt = c;
resId = r;
lyrics = l;
inflater = (LayoutInflater) cxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setLyrics(String[] ls) {
lyrics = ls;
}
public int getCount() {
return lyrics.length;
}
public Object getItem(int position) {
return lyrics[position];
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "===============================");
View view;
TextView text;
if (convertView == null) {
view = inflater.inflate(resId, parent, false);
} else {
view = convertView;
}
view.setMinimumHeight(position == 0 ? marginHeight
: (position == lyrics.length - 1) ? listHeight : 0);
view.requestLayout();
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource
// is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
String item = (String) getItem(position);
text.setText(item);
text.setTextColorQuiet((curLrc + 1) == position ? colorHighlight : colorNormal);
return view;
}
}
}