diff options
Diffstat (limited to 'platform')
42 files changed, 3875 insertions, 35 deletions
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java new file mode 100644 index 00000000..5da38c2e --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java @@ -0,0 +1,204 @@ +package com.artifex.mupdf.android; + + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Point; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.artifex.mupdf.fitz.R; + +public class ColorDialog implements View.OnTouchListener, PopupWindow.OnDismissListener { + + // these are set in the constructor. + private final Context mContext; + private final View mAnchor; + private final ColorChangedListener mListener; + private final int mDialogType; + private boolean mAutoDismiss = false; + + // only one ColorDialog at a time please. + private static ColorDialog singleton = null; + + // a popup window for us. + private PopupWindow popupWindow; + + // two different variants of this dilog + public static final int FG_COLORS = 1; + public static final int BG_COLORS = 2; + + // allowable foreground colors. These come from UE2FileViewerApp + private final String mFgColors[] = {"#000000", "#FFFFFF", "#D8D8D8", "#808080", "#EEECE1", "#1F497D", + "#0070C0", "#C0504D", "#9BBB59", "#8064A2", "#4BACC6", "#F79646", "#FF0000", + "#FFFF00", "#DBE5F1", "#F2DCDB", "#EBF1DD", "#00B050"}; + + // constructor + public ColorDialog(int dialogType, Context context, View anchor, ColorChangedListener listener, boolean autoDismiss) + { + mContext = context; + mAnchor = anchor; + mListener = listener; + mDialogType = dialogType; + mAutoDismiss = autoDismiss; + } + + // whether or not to show the title + private boolean mShowTitle = true; + public void setShowTitle(boolean val) {mShowTitle = val;} + + // time to show the dialog + public void show() + { + // remember us + singleton = this; + + // get the layout + View popupView = LayoutInflater.from(mContext).inflate(R.layout.colors, null); + + // set the title + TextView tv = (TextView)popupView.findViewById(R.id.color_dialog_title); + if (mShowTitle) { + if (mDialogType == BG_COLORS) + tv.setText(mContext.getString(R.string.background)); + else + tv.setText(mContext.getString(R.string.color)); + } + else { + tv.setVisibility(View.GONE); + } + + // choose the list of colors to use + String colors[] = mFgColors; + + // get the three rows of buttons from the layout + LinearLayout rows[] = new LinearLayout[3]; + rows[0] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row1); + rows[1] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row2); + rows[2] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row3); + + // Set up a button for each color. + // Hide buttons for which there are no colors. + int icolor = 0; + int irow; + for (irow=0; irow<rows.length; irow++) { + LinearLayout row = rows[irow]; + int count = row.getChildCount(); + for (int i=0; i<count; i++) { + Button button = (Button)row.getChildAt(i); + if (icolor+1 <= colors.length) { + button.setVisibility(View.VISIBLE); + button.setBackgroundColor(Color.parseColor(colors[icolor])); + button.setTag(colors[icolor]); + button.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) + { + mListener.onColorChanged((String)v.getTag()); + if (mAutoDismiss) + dismiss(); + } + }); + } + else { + button.setVisibility(View.GONE); + } + icolor++; + } + } + + // Set up the transaparent button if we're doing background colors. + Button tpb = (Button) popupView.findViewById(R.id.transparent_color_button); + if (mDialogType==ColorDialog.BG_COLORS) { + tpb.setVisibility(View.VISIBLE); + tpb.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) + { + mListener.onColorChanged((String)v.getTag()); + } + }); + } + else + tpb.setVisibility(View.GONE); + + // get screen width + WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + int screenWidth = metrics.widthPixels; + + // put everything in a popup window and show it. + // by default that's the upper right corner, but the dialog is draggable. + popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + popupWindow.setFocusable(true); + popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + popupWindow.showAtLocation(mAnchor, Gravity.TOP|Gravity.LEFT, screenWidth-popupView.getMeasuredWidth()-15, 100); + popupWindow.setClippingEnabled(false); + // this enables dragging + popupView.setOnTouchListener(this); + // this allows us to know when the popup is being dismissed + popupWindow.setOnDismissListener(this); + } + + // internal function to dismiss the popup. + public void dismiss() + { + popupWindow.dismiss(); + singleton = null; + } + + // static function to dismiss the popup. + static public void finish() + { + if (singleton!=null) + singleton.dismiss(); + } + + // this function is called when the user taps outside the popup. + // we make sure to dismiss it properly. + @Override + public void onDismiss() { + dismiss(); + } + + // we're an onTouch listener for the popup window. + // we use that to allow for dragging the popup around. + + private int start[]; + private final Point down = new Point(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + + start = new int[2]; + popupWindow.getContentView().getLocationOnScreen(start); + down.set((int) event.getRawX(), (int) event.getRawY()); + break; + + case MotionEvent.ACTION_MOVE: + + int dx = down.x - (int)event.getRawX(); + int dy = down.y - (int)event.getRawY(); + popupWindow.update(start[0]-dx, start[1]-dy,-1, -1, true); + break; + } + return true; + } + + public interface ColorChangedListener { + void onColorChanged(String color); + } + +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java index e56ed43d..26624ab5 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java @@ -2,6 +2,7 @@ package com.artifex.mupdf.android; import android.app.Activity; import android.content.Context; +import android.graphics.Color; import android.graphics.PorterDuff; import android.util.AttributeSet; import android.util.Log; @@ -26,7 +27,7 @@ import com.artifex.mupdf.fitz.Link; import com.artifex.mupdf.fitz.Outline; import com.artifex.mupdf.fitz.R; -public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeListener, View.OnClickListener +public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeListener, View.OnClickListener, DocView.SelectionChangeListener { private DocView mDocView; private DocReflowView mDocReflowView; @@ -55,7 +56,15 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL private ImageButton mPrintButton; private ImageButton mShareButton; private ImageButton mOpenInButton; + private ImageButton mToggleAnnotButton; + private ImageButton mHighlightButton; + private ImageButton mDeleteButton; + + private ImageButton mNoteButton; + private ImageButton mDrawButton; + private ImageButton mLineColorButton; + private ImageButton mLineThicknessButton; public DocActivityView(Context context) { @@ -356,6 +365,23 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL mToggleAnnotButton = (ImageButton)findViewById(R.id.show_annot_button); mToggleAnnotButton.setOnClickListener(this); + mHighlightButton = (ImageButton)findViewById(R.id.highlight_button); + mHighlightButton.setOnClickListener(this); + + mNoteButton = (ImageButton)findViewById(R.id.note_button); + mNoteButton.setOnClickListener(this); + + mDrawButton = (ImageButton)findViewById(R.id.draw_button); + mDrawButton.setOnClickListener(this); + + mLineColorButton = (ImageButton)findViewById(R.id.line_color_button); + mLineColorButton.setOnClickListener(this); + + mLineThicknessButton = (ImageButton)findViewById(R.id.line_thickness_button); + mLineThicknessButton.setOnClickListener(this); + + mDeleteButton = (ImageButton)findViewById(R.id.delete_button); + mDeleteButton.setOnClickListener(this); mDoc = new Document(path); @@ -409,6 +435,10 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL { mDocPagesView.clone(mDocView); } + + mHighlightButton.setEnabled(false); + mDocView.setSelectionChangeListener(this); + onSelectionChanged(); } public void showUI(boolean show) @@ -481,10 +511,12 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL { if (v == mReflowButton) onReflowButton(); + if (v == mFirstPageButton) onFirstPageButton(); if (v == mLastPageButton) onLastPageButton(); + if (v == mSearchButton) onShowSearch(); if (v == mSearchText) @@ -493,6 +525,7 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL onSearchNextButton(); if (v == mSearchPreviousButton) onSearchPreviousButton(); + if (v == mBackButton) onBackButton(); @@ -506,8 +539,22 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL onShareButton(); if (v == mOpenInButton) onOpenInButton(); + if (v == mToggleAnnotButton) onToggleAnnotButton(); + if (v == mHighlightButton) + onHighlightButton(); + if (v == mDeleteButton) + onDeleteButton(); + + if (v == mNoteButton) + onNoteButton(); + if (v == mDrawButton) + onDrawButton(); + if (v == mLineColorButton) + onLineColorButton(); + if (v == mLineThicknessButton) + onLineThicknessButton(); } public void onSearchNextButton() @@ -647,6 +694,83 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL mDocView.toggleAnnotations(); } + private void onHighlightButton() + { + mDocView.onHighlight(); + } + + private void onNoteButton() + { + mDocView.onNoteMode(); + } + + private void onDrawButton() + { + mDocView.onDrawMode(); + } + + private void onLineColorButton() + { + if (mDocView.getDrawMode() || mDocView.hasInkAnnotationSelected()) + { + ColorDialog dlg = new ColorDialog(ColorDialog.BG_COLORS, + getContext(), mLineColorButton, new ColorDialog.ColorChangedListener() + { + @Override + public void onColorChanged(String color) + { + int icolor = Color.parseColor(color); + mDocView.setInkLineColor(icolor); + mLineColorButton.setColorFilter(icolor, PorterDuff.Mode.SRC_IN); + } + }, true); + dlg.setShowTitle(false); + dlg.show(); + } + } + + private void onLineThicknessButton() + { + if (mDocView.getDrawMode() || mDocView.hasInkAnnotationSelected()) + { + float val = mDocView.getInkLineThickness(); + LineWidthDialog.show(getContext(), mLineThicknessButton, val, + new LineWidthDialog.WidthChangedListener() + { + @Override + public void onWidthChanged(float value) + { + mDocView.setInkLineThickness(value); + } + }); + } + } + + private void onDeleteButton() + { + mDocView.onDelete(); + } + + public void onSelectionChanged() + { + boolean hasSel = mDocView.hasSelection(); + boolean hasInkAnnotSel = mDocView.hasInkAnnotationSelected(); + + mHighlightButton.setEnabled(hasSel); + + boolean noteMode = mDocView.getNoteMode(); + mNoteButton.setSelected(noteMode); + findViewById(R.id.note_holder).setSelected(noteMode); + + boolean drawMode = mDocView.getDrawMode(); + mDrawButton.setSelected(drawMode); + mLineColorButton.setEnabled(drawMode || hasInkAnnotSel); + mLineThicknessButton.setEnabled(drawMode || hasInkAnnotSel); + mDeleteButton.setEnabled(!drawMode && hasInkAnnotSel); + + findViewById(R.id.draw_tools_holder).setSelected(drawMode); + } + private OnDoneListener mDoneListener = null; public void setOnDoneListener(OnDoneListener l) {mDoneListener = l;} public interface OnDoneListener diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java index 50f526f6..0bde1f03 100755 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java @@ -7,10 +7,13 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.RectF; import android.os.AsyncTask; import android.support.v4.content.ContextCompat; import android.util.Log; +import android.util.TypedValue; import android.view.KeyEvent.Callback; import android.view.View; import android.view.ViewGroup; @@ -27,6 +30,8 @@ import com.artifex.mupdf.fitz.StructuredText; import com.artifex.mupdf.fitz.android.AndroidDrawDevice; import java.util.ArrayList; +import java.util.Iterator; +import java.util.ListIterator; public class DocPageView extends View implements Callback { @@ -550,6 +555,17 @@ public class DocPageView extends View implements Callback canvas.drawRect(mHighlightingRect, mSearchHighlightPainter); } + // draw ink annotations + if (mInkAnnots != null) + { + Iterator<InkAnnotation> it = mInkAnnots.iterator(); + while (it.hasNext()) + { + InkAnnotation annot = it.next(); + annot.draw(canvas); + } + } + // draw blue dot if (isMostVisible) { @@ -668,11 +684,24 @@ public class DocPageView extends View implements Callback return screenToPage(p.x, p.y); } + public PointF screenToPage(PointF p) + { + Point pp = screenToPage(new Point((int)p.x, (int)p.y)); + return new PointF(pp.x, pp.y); + } + private double getFactor() { return mZoom * mScale * mResolution / 72f; } + private double pageToScreen(double val) + { + double factor = getFactor(); + + return factor * val; + } + private Point screenToPage(int screenX, int screenY) { // convert to view-relative @@ -702,6 +731,36 @@ public class DocPageView extends View implements Callback return new Point(viewX, viewY); } + public void pageToView(Point pageP, Point viewP) + { + double factor = getFactor(); + + int x = (int) (((double) pageP.x) * factor); + int y = (int) (((double) pageP.y) * factor); + + viewP.set(x, y); + } + + public void pageToView(PointF pageP, PointF viewP) + { + double factor = getFactor(); + + float x = (pageP.x * (float)factor); + float y = (pageP.y * (float)factor); + + viewP.set(x, y); + } + + private PointF pageToView(PointF pageP) + { + double factor = getFactor(); + + float x = (pageP.x * (float)factor); + float y = (pageP.y * (float)factor); + + return new PointF(x, y); + } + public void pageToView(Rect pageR, Rect viewR) { double factor = getFactor(); @@ -751,6 +810,35 @@ public class DocPageView extends View implements Callback public boolean onSingleTap(int x, int y) { + // see if an ink annotation has been tapped on + if (mInkAnnots != null) + { + boolean hit = false; + + // switch to page coordinates + Point pt = screenToPage(x, y); + + // iterate in reverse order + ListIterator<InkAnnotation> li = mInkAnnots.listIterator(mInkAnnots.size()); + while (li.hasPrevious()) + { + InkAnnotation annot = li.previous(); + annot.setSelected(false); + if(annot.hitTest(pt)) + { + if (!hit) + annot.setSelected(true); + hit = true; + } + } + + if (hit) + { + invalidate(); + return true; + } + } + // NOTE: when double-tapping, a single-tap will also happen first. // so that must be safe to do. @@ -765,17 +853,53 @@ public class DocPageView extends View implements Callback // during layout, a DocView-relative rect is calculated and stashed here. private final Rect mChildRect = new Rect(); - public void setChildRect(Rect r) { mChildRect.set(r); } - public Rect getChildRect() { return mChildRect; } + private ArrayList<InkAnnotation> mInkAnnots; + + public void startDraw(float x, float y, int color, float thickness) + { + // create annotation list + if (mInkAnnots == null) + mInkAnnots = new ArrayList<>(); + + // add a new annotation to the list + // convert thickness from pt to pixels + float px = thickness * (float)getFactor(); + InkAnnotation annot = new InkAnnotation(color, px); + mInkAnnots.add(annot); + + // add first point to the new annot, in page coords + PointF pScreen = new PointF(x, y); + PointF pPage = screenToPage(pScreen); + annot.add(pPage); + + invalidate(); + } + + public void continueDraw(float x, float y) + { + if (mInkAnnots!=null && mInkAnnots.size()>0) + { + // get the most recent annotation + InkAnnotation annot = mInkAnnots.get(mInkAnnots.size()-1); + + // add the point, in page coords + PointF pScreen = new PointF(x, y); + PointF pPage = screenToPage(pScreen); + annot.add(pPage); + + invalidate(); + } + } + private class RenderTaskParams { RenderTaskParams(RenderListener listener, Matrix ctm, Bitmap bitmap, @@ -865,4 +989,247 @@ public class DocPageView extends View implements Callback } } + private InkAnnotation getSelectedInkAnnotation() + { + if (mInkAnnots != null) + { + // iterate in reverse order + ListIterator<InkAnnotation> li = mInkAnnots.listIterator(mInkAnnots.size()); + while (li.hasPrevious()) + { + InkAnnotation annot = li.previous(); + if (annot.isSelected()) + return annot; + } + } + + return null; + } + + public boolean hasInkAnnotationSelected() + { + return (getSelectedInkAnnotation() != null); + } + + public void setSelectedInkLineColor(int val) + { + InkAnnotation annot = getSelectedInkAnnotation(); + if (annot != null) + { + annot.setLineColor(val); + invalidate(); + } + } + + public void setSelectedInkLineThickness(float val) + { + InkAnnotation annot = getSelectedInkAnnotation(); + if (annot != null) + { + float px = val * (float)getFactor(); + annot.setLineThickness(px); + invalidate(); + } + } + + public void deleteSelectedInkAnnotation() + { + InkAnnotation annot = getSelectedInkAnnotation(); + if (annot != null) + { + mInkAnnots.remove(annot); + invalidate(); + } + } + +//----------------------------------------------------- + + public class InkAnnotation + { + private float mLineThickness; + public void setLineThickness(float lineThickness) {mLineThickness = lineThickness;} + + private int mLineColor; + public void setLineColor(int lineColor) {mLineColor = lineColor;} + + private boolean mSelected = false; + public void setSelected(boolean sel) {mSelected = sel;} + public boolean isSelected() {return mSelected;} + + private ArrayList<PointF> mArc; + + // touch margin is 2mm either side of the arc + private final float HIT_MARGIN = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 2, + getResources().getDisplayMetrics()); + + public InkAnnotation(int lineColor, float lineThickness) + { + mLineColor = lineColor; + mLineThickness = lineThickness; + mArc = new ArrayList<>(); + } + + public void add(PointF p) + { + mArc.add(p); + } + + public boolean hitTest(Point pt) + { + PointF p1, p2; + PointF ptf = new PointF(pt); + + if (mArc.size() >= 2) + { + Iterator<PointF> iit = mArc.iterator(); + p1 = iit.next(); + while (iit.hasNext()) + { + p2 = iit.next(); + + // test + double d = LineToPointDistance2D(p1, p2, ptf); + d = pageToScreen(d); + + if (d <= HIT_MARGIN) + { + return true; + } + + p1 = p2; + } + } + else + { + p1 = mArc.get(0); + + // test + double d = Distance(p1, ptf); + d = pageToScreen(d); + + if (d <= HIT_MARGIN) + { + return true; + } + + } + + return false; + } + + public void draw(Canvas canvas) + { + Path path = new Path(); + PointF pPage; + PointF pView = new PointF(); + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setDither(true); + paint.setStrokeJoin(Paint.Join.ROUND); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setStyle(Paint.Style.FILL); + paint.setStrokeWidth(mLineThickness * mScale); + paint.setColor(mLineColor); + + RectF bounds = new RectF(); + + if (mArc.size() >= 2) + { + Iterator<PointF> iit = mArc.iterator(); + pPage = iit.next(); + pageToView(pPage, pView); + float mX = pView.x; + float mY = pView.y; + path.moveTo(mX, mY); + while (iit.hasNext()) + { + pPage = iit.next(); + pageToView(pPage, pView); + float x = pView.x; + float y = pView.y; + path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + path.lineTo(mX, mY); + paint.setStyle(Paint.Style.STROKE); + canvas.drawPath(path, paint); + + path.computeBounds(bounds, true); + } + else + { + pPage = mArc.get(0); + pageToView(pPage, pView); + float r = mLineThickness * mScale / 2; + bounds.set(pView.x-r, pView.y-r, pView.x+r, pView.y+r); + canvas.drawCircle(pView.x, pView.y, r, paint); + } + + if (isSelected()) + { + // expand the bounds to account for ine thickness + float px = mLineThickness * mScale / 2; + bounds.inset(-px, -px); + + mHighlightingRect.set((int)bounds.left, (int)bounds.top, (int)bounds.right, (int)bounds.bottom); + canvas.drawRect(mHighlightingRect, mSelectionHighlightPainter); + } + } + + private double DotProduct(PointF pointA, PointF pointB, PointF pointC) + { + double[] AB = new double[2]; + double[] BC = new double[2]; + AB[0] = pointB.x - pointA.x; + AB[1] = pointB.y - pointA.y; + BC[0] = pointC.x - pointB.x; + BC[1] = pointC.y - pointB.y; + double dot = AB[0] * BC[0] + AB[1] * BC[1]; + + return dot; + } + + //Compute the cross product AB x AC + private double CrossProduct(PointF pointA, PointF pointB, PointF pointC) + { + double[] AB = new double[2]; + double[] AC = new double[2]; + AB[0] = pointB.x - pointA.x; + AB[1] = pointB.y - pointA.y; + AC[0] = pointC.x - pointA.x; + AC[1] = pointC.y - pointA.y; + double cross = AB[0] * AC[1] - AB[1] * AC[0]; + + return cross; + } + + //Compute the distance from A to B + double Distance(PointF pointA, PointF pointB) + { + double d1 = pointA.x - pointB.x; + double d2 = pointA.y - pointB.y; + + return Math.sqrt(d1 * d1 + d2 * d2); + } + + //Compute the distance from AB to C + double LineToPointDistance2D(PointF pointA, PointF pointB, PointF pointC) + { + double dist = CrossProduct(pointA, pointB, pointC) / Distance(pointA, pointB); + if (true) + { + double dot1 = DotProduct(pointA, pointB, pointC); + if (dot1 > 0) + return Distance(pointB, pointC); + + double dot2 = DotProduct(pointB, pointA, pointC); + if (dot2 > 0) + return Distance(pointA, pointC); + } + return Math.abs(dist); + } + } + } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java index 346b887b..11ad2601 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java @@ -7,8 +7,9 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; -import android.view.View; +import android.view.MotionEvent; import android.widget.RelativeLayout; +import android.widget.Toast; import com.artifex.mupdf.fitz.Page; import com.artifex.mupdf.fitz.R; @@ -109,11 +110,6 @@ public class DocView extends DocViewBase implements DragHandleListener mSelectionHandleBottomRight.show(show); } - private boolean getSelectionHandlesVisible() - { - return (mSelectionHandleTopLeft.getVisibility() == View.VISIBLE); - } - @Override protected void doSingleTap(float fx, float fy) { @@ -123,18 +119,20 @@ public class DocView extends DocViewBase implements DragHandleListener if (dpv == null) return; - if (getSelectionHandlesVisible()) + if (!getDrawMode()) { - // hide handles and remove selection from pages - showSelectionHandles(false); - int numPages = getPageCount(); - for (int i = 0; i < numPages; i++) + if (dpv.onSingleTap(p.x, p.y)) { - DocPageView cv = (DocPageView) getOrCreateChild(i); - cv.removeSelection(); - if (cv.isReallyVisible()) - cv.invalidate(); + onChangeSelection(); + return; } + onChangeSelection(); + } + + if (hasSelection()) + { + clearSelection(); + onChangeSelection(); } else { @@ -142,20 +140,88 @@ public class DocView extends DocViewBase implements DragHandleListener Rect r = dpv.selectWord(p); if (r != null) { - // show handles showSelectionHandles(true); - // set highlight boundaries selectionStartPage = dpv; selectionStartLoc.set(r.left, r.top); selectionEndPage = dpv; selectionEndLoc.set(r.right, r.bottom); moveHandlesToCorners(); + + onChangeSelection(); } } } + private void clearSelection() + { + selectionStartPage = null; + selectionEndPage = null; + showSelectionHandles(false); + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.removeSelection(); + if (cv.isReallyVisible()) + cv.invalidate(); + } + } + + public boolean hasSelection() + { + return (selectionStartPage != null && selectionEndPage != null); + } + + public boolean hasInkAnnotationSelected() + { + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.hasInkAnnotationSelected()) + return true; + } + + return false; + } + + public void setSelectedInkLineColor(int val) + { + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.hasInkAnnotationSelected()) + cv.setSelectedInkLineColor(val); + } + } + + public void setSelectedInkLineThickness(float val) + { + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.hasInkAnnotationSelected()) + cv.setSelectedInkLineThickness(val); + } + } + + private void onChangeSelection() + { + if (mSelectionChangeListener != null) + mSelectionChangeListener.onSelectionChanged(); + } + + private SelectionChangeListener mSelectionChangeListener = null; + public void setSelectionChangeListener (SelectionChangeListener l) {mSelectionChangeListener = l;} + public interface SelectionChangeListener + { + public void onSelectionChanged(); + } + @Override protected void doDoubleTap(float fx, float fy) { @@ -214,6 +280,7 @@ public class DocView extends DocViewBase implements DragHandleListener selectionStartPage = pageView1; p1 = pageView1.screenToPage(p1); selectionStartLoc.set(p1.x, p1.y); + onChangeSelection(); } } @@ -228,6 +295,7 @@ public class DocView extends DocViewBase implements DragHandleListener selectionEndPage = pageView2; p2 = pageView2.screenToPage(p2); selectionEndLoc.set(p2.x, p2.y); + onChangeSelection(); } } @@ -417,4 +485,129 @@ public class DocView extends DocViewBase implements DragHandleListener } } + public void onHighlight() + { + if (hasSelection()) + { + Toast.makeText(getContext(),"onHighlight", Toast.LENGTH_SHORT).show(); + } + } + + private boolean mNoteMode = false; + public boolean getNoteMode() {return mNoteMode;} + public void onNoteMode() + { + mNoteMode = !mNoteMode; + mDrawMode = false; + clearSelection(); + onChangeSelection(); + } + + private boolean mDrawMode = false; + public boolean getDrawMode() {return mDrawMode;} + public void onDrawMode() + { + mDrawMode = !mDrawMode; + mNoteMode = false; + clearSelection(); + onChangeSelection(); + } + + public void onDelete() + { + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.hasInkAnnotationSelected()) + cv.deleteSelectedInkAnnotation(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (mDrawMode) + { + float x = event.getX(); + float y = event.getY(); + switch (event.getAction()) + { + case MotionEvent.ACTION_DOWN: + touch_start(x, y); + break; + case MotionEvent.ACTION_MOVE: + touch_move(x, y); + break; + case MotionEvent.ACTION_UP: + touch_up(); + break; + } + + return true; + } + + return super.onTouchEvent(event); + } + + private float mX, mY; + private static final float TOUCH_TOLERANCE = 2; + + private int mCurrentInkLineColor = 0xFFFF0000; + public void setInkLineColor(int val) + { + mCurrentInkLineColor=val; + + // also change any selected annotation + if (hasInkAnnotationSelected()) + setSelectedInkLineColor(val); + } + public int getInkLineColor() {return mCurrentInkLineColor;} + + private float mCurrentInkLineThickness = 4.5f; + public float getInkLineThickness() {return mCurrentInkLineThickness;} + public void setInkLineThickness(float val) + { + mCurrentInkLineThickness=val; + + // also change any selected annotation + if (hasInkAnnotationSelected()) + setSelectedInkLineThickness(val); + } + + private void touch_start(float x, float y) + { + Point p = eventToScreen(x, y); + final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); + if (dpv != null) + { + dpv.startDraw(p.x, p.y, mCurrentInkLineColor, mCurrentInkLineThickness); + } + + mX = x; + mY = y; + } + + private void touch_move(float x, float y) { + + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) + { + Point p = eventToScreen(x, y); + final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); + if (dpv != null) + { + dpv.continueDraw(p.x, p.y); + } + mX = x; + mY = y; + } + } + + private void touch_up() + { + // NOOP + } + } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java new file mode 100644 index 00000000..beea9d98 --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java @@ -0,0 +1,128 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.database.DataSetObserver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.artifex.mupdf.fitz.R; + +import kankan.wheel.widget.OnWheelScrollListener; +import kankan.wheel.widget.WheelView; +import kankan.wheel.widget.adapters.WheelViewAdapter; + +public class LineWidthDialog +{ + private static final int POPUP_OFFSET = 30; + + private static final float values[] = new float[] {0.25f, 0.5f, 1, 1.5f, 3, 4.5f, 6, 8, 12, 18, 24}; + + public static void show(Context context, View anchor, float val, final WidthChangedListener listener) + { + float currentValue = val; + + View layout = View.inflate(context, R.layout.line_width_dialog, null); + + View wv = layout.findViewById(R.id.wheel); + WheelView wheel = (WheelView)wv; + + final LineWidthAdapter adapter = new LineWidthAdapter(context, values); + + wheel.setViewAdapter(adapter); + wheel.setVisibleItems(5); + + wheel.setCurrentItem(0); + for (int i=0;i<values.length;i++) + { + if (values[i] == currentValue) + wheel.setCurrentItem(i); + } + + wheel.addScrollingListener(new OnWheelScrollListener() { + @Override + public void onScrollingStarted(WheelView wheel) {} + + @Override + public void onScrollingFinished(WheelView wheel) { + listener.onWidthChanged(values[wheel.getCurrentItem()]); + } + }); + + // make a popup window + final PopupWindow popup = new PopupWindow(layout, + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + popup.setFocusable(true); + + // now show the popup + popup.showAsDropDown(anchor,POPUP_OFFSET,POPUP_OFFSET); + } + + public static class LineWidthAdapter implements WheelViewAdapter + { + public LineWidthAdapter(Context context, float values[]) { + super(); + mValues = values; + mContext = context; + } + + private Context mContext; + private float mValues[]; + + @Override + public View getItem(int position, View convertView, ViewGroup parent) + { + // make a View if needed. + if (convertView == null) + { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.line_width_item, parent, false); + } + + // get the value + float value = values[position]; + + // set the text string + TextView tv = ((TextView)convertView.findViewById(R.id.value)); + if(value == (int) value) + tv.setText(String.format("%d pt",(int)value)); + else + tv.setText(String.format("%s pt",value)); + + // adjust the height of the line + int h = (int)(value*3f/2f); + if (h<1) + h = 1; + View bar = ((View)convertView.findViewById(R.id.bar)); + bar.getLayoutParams().height = h; + bar.setLayoutParams(bar.getLayoutParams()); + + return convertView; + } + + @Override + public int getItemsCount() { + return mValues.length; + } + + @Override + public View getEmptyItem(View convertView, ViewGroup parent) { + return null; + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + } + } + + public interface WidthChangedListener { + void onWidthChanged(float value); + } + +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java new file mode 100644 index 00000000..5760df48 --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java @@ -0,0 +1,31 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.support.annotation.ColorInt; +import android.util.AttributeSet; +import android.widget.ImageButton; + +public class ToolbarButton extends ImageButton +{ + // Color.GRAY (0xFF888888) is too dark. Use something lighter. + @ColorInt + private static final int MYGRAY = 0xFFAAAAAA; + + public ToolbarButton(Context context) { + super(context); + } + + public ToolbarButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override public void setEnabled(boolean enabled) + { + super.setEnabled(enabled); + if (enabled) + setColorFilter(null); + else + setColorFilter(MYGRAY, PorterDuff.Mode.SRC_IN); + } +} diff --git a/platform/android/example/mupdf/src/main/res/drawable/colors.xml b/platform/android/example/mupdf/src/main/res/drawable/colors.xml new file mode 100644 index 00000000..eff1250e --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/colors.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <stroke android:width="1dp" android:color="#797979" /> + <solid android:color="@color/bluish_grey" /> + <corners + android:radius="7dp" /> + <padding + android:top="10dp" + android:left="10dp" + android:right="10dp" + android:bottom="10dp" + /> +</shape> diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml new file mode 100755 index 00000000..d027b030 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="1.331" + android:pathData="M 25 9.92 C 33.3284540274 9.92 40.08 16.6715459726 40.08 25 C 40.08 33.3284540274 33.3284540274 40.08 25 40.08 C 16.6715459726 40.08 9.92 33.3284540274 9.92 25 C 9.92 16.6715459726 16.6715459726 9.92 25 9.92 Z" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.774" + android:pathData="M 17.622 32.934 L 32.257 18.299" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.774" + android:pathData="M 32.257 32.934 L 17.622 18.297" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml new file mode 100644 index 00000000..808efdaa --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M33.1075,16.6386 L38.7232,19.2633 L31.7612,34.159 L26.1455,31.5343 +L33.1075,16.6386 Z" /> + <path + android:fillColor="#333333" + android:pathData="M25.735,32.435 L31.334,35.052 L25.884,39.634 Z" /> + <path + android:fillColor="#333333" + android:pathData="M39.29,18.067 L41.299,13.773 C41.358,13.646,41.302,13.495,41.176,13.435 +L36.02,11.025 C35.893,10.965,35.741,11.021,35.682,11.148 L33.675,15.442 +L39.29,18.067 Z" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.04" + android:strokeLineCap="round" + android:pathData="M8.738,10.366 C8.738,10.366,26.245,13.833,24.685,17.3 +C23.125,20.765,9.953,21.286,8.738,25.793 C7.525,30.3,24.859,39.313,24.859,39.313" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml new file mode 100755 index 00000000..f42b88d9 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M 20.629 22.61 L 27.374 29.355 L 22.483 30.848 L 19.701 28.068 Z" /> + <path + android:fillColor="#333333" + android:pathData="M27.909,28.668l12.17-12.032c0.153-0.154,0.153-0.404,0.001-0.557l-6.199-6.251 +c-0.154-0.154-0.404-0.155-0.559-0.001L21.154,21.861L27.909,28.668z" /> + <path + android:fillColor="#333333" + android:pathData="M 18.992 28.629 L 14.841 32.359 L 20.726 32.404 L 21.803 31.594 Z" /> + <path + android:fillColor="#333333" + android:pathData="M 9.76 34.59 H 40.24 V 40.287 H 9.76 V 34.59 Z" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml new file mode 100755 index 00000000..1c79066a --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#81DBE7" + android:strokeWidth="1.56" + android:pathData="M40.797,35.303 +c0,1.518-1.229,2.747-2.747,2.747h-26.1c-1.516,0-2.747-1.229-2.747-2.747V14.698c0-1.518,1.231-2.748,2.747-2.748h26.1 +c1.519,0,2.747,1.23,2.747,2.748V35.303z" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml new file mode 100755 index 00000000..1e48a6c9 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="2.6" + android:pathData="M40.797,35.303 +c0,1.518-1.229,2.747-2.747,2.747h-26.1c-1.517,0-2.747-1.229-2.747-2.747V14.698c0-1.518,1.23-2.748,2.747-2.748h26.1 +c1.519,0,2.747,1.23,2.747,2.748V35.303z" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml new file mode 100755 index 00000000..1f5732e3 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="1.372" + android:strokeMiterLimit="10" + android:pathData="M12.204,32.945 +H10.09c-0.632,0-1.145-0.516-1.145-1.145V11.896c0-0.63,0.513-1.143,1.145-1.143h29.821c0.632,0,1.144,0.513,1.144,1.143v19.904 +c0,0.629-0.512,1.145-1.144,1.145H18.507l-6.303,6.301V32.945z" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.769" + android:strokeMiterLimit="10" + android:pathData="M 16.993 17.862 L 30.559 17.862" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.769" + android:strokeMiterLimit="10" + android:pathData="M 16.993 26.564 L 28.381 26.564" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.769" + android:strokeMiterLimit="10" + android:pathData="M 16.993 22.213 L 31.885 22.213" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml b/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml new file mode 100644 index 00000000..5154168b --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <stroke android:width="1dp" android:color="@color/formula_popup_background" /> + <solid android:color="@color/number_format_popup_background" /> + <corners + android:radius="7dp" /> + <padding + android:top="5dp" + android:left="5dp" + android:right="5dp" + android:bottom="5dp" + /> +</shape> diff --git a/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml new file mode 100644 index 00000000..d624e85f --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape> + <solid android:color="@color/white" /> + <corners android:radius="12dp"/> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> + <item> + <shape> + <solid android:color="@color/button_normal" /> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml b/platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml new file mode 100644 index 00000000..fdf3d369 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item> + <shape> + <stroke android:color="@color/black" android:width="1dp" /> + <solid android:color="@color/white" /> + </shape> + </item> + + <item> + <rotate + android:fromDegrees="45"> + <shape + android:shape="line" > + <stroke android:color="@color/red" android:width="4dp" /> + </shape> + </rotate> + </item> + +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml b/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml new file mode 100644 index 00000000..f9fdd27a --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Android Wheel Control. + http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html + + Copyright 2010 Yuri Kanivets + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="rectangle"> + <solid android:color="#ffffff" /> + <stroke android:width="1dp" android:color="#ffffff" /> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml b/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml new file mode 100644 index 00000000..45f7f5c9 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Android Wheel Control. + http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html + + Copyright 2010 Yuri Kanivets + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#00ffffff"/> + <stroke android:width="1dp" + android:dashWidth="3px" + android:dashGap="3px" + android:color="#ff000000" /> +</shape> diff --git a/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml index 767a27b0..50814a44 100644 --- a/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml +++ b/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml @@ -19,27 +19,168 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + android:gravity="center_horizontal" android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:layout_centerVertical="true" android:background="@drawable/toolbar_button" - android:id="@+id/show_annot_button" - android:src="@drawable/icon_toggle_annotations" /> + android:id="@+id/highlight_button" + android:src="@drawable/icon_highlight" /> <TextView - android:layout_width="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="HIGHLIGHT"/> + </LinearLayout> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/draw_tools_holder" + android:background="@drawable/toolbar_button2" + > + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button2" + android:id="@+id/draw_button" + android:src="@drawable/icon_draw" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="DRAW"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button2" + android:id="@+id/line_color_button" + android:src="@drawable/icon_line_color" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text=""/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button2" + android:id="@+id/line_thickness_button" + android:src="@drawable/icon_line_thickness" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text=""/> + </LinearLayout> + + + </LinearLayout> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/note_holder" + android:background="@drawable/toolbar_button2" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button2" + android:id="@+id/note_button" + android:src="@drawable/icon_note" /> + + <TextView + android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textColor="@color/black" android:textSize="11sp" - android:text="TOGGLE"/> + android:text="NOTE"/> </LinearLayout> + + </LinearLayout> + + <!--a divider--> <View android:layout_width="1dp" @@ -47,6 +188,78 @@ android:layout_height="match_parent" android:background="#FF8E8F90" /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button" + android:id="@+id/delete_button" + android:src="@drawable/icon_delete" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="DELETE"/> + </LinearLayout> + + + <!-- + this layout holds some buttons for testing + normally it's not shown. + --> + + <LinearLayout + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + <com.artifex.mupdf.android.ToolbarButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button" + android:id="@+id/show_annot_button" + android:src="@drawable/icon_toggle_annotations" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="TOGGLE"/> + </LinearLayout> + + </LinearLayout> + </LinearLayout> </HorizontalScrollView> diff --git a/platform/android/example/mupdf/src/main/res/layout/colors.xml b/platform/android/example/mupdf/src/main/res/layout/colors.xml new file mode 100644 index 00000000..d7d7aa91 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/colors.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/colors" + android:orientation="vertical" + > + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="false" + android:layout_alignParentTop="true" + android:id="@+id/font_panel"> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="@color/bluish_grey" + android:textColor="#FFFFFFFF" + android:textSize="12pt" + android:id="@+id/color_dialog_title" /> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/fontcolors_row1" > + + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + + </LinearLayout> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/fontcolors_row2"> + + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + + </LinearLayout> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/fontcolors_row3"> + + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/> + + <Button style="@style/font_color_button" + android:id="@+id/transparent_color_button" + android:background="@drawable/transparent_color_swatch" + android:tag="transparent"/> + + </LinearLayout> + + </LinearLayout> + + </RelativeLayout> + + <!--<Button--> + <!--android:layout_width="wrap_content"--> + <!--android:layout_height="wrap_content"--> + <!--android:text="@string/done"--> + <!--android:id="@+id/done_button"--> + <!--android:background="@drawable/button"--> + <!--android:textColor="#FFFFFFFF"--> + <!--android:layout_gravity="right"/>--> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml index 6468c9d0..a35d2ed9 100644 --- a/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml +++ b/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml @@ -22,7 +22,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -47,7 +47,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -79,7 +79,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -104,7 +104,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -129,7 +129,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" diff --git a/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml b/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml new file mode 100644 index 00000000..5c47ab1d --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@drawable/number_format_popup"> + + <kankan.wheel.widget.WheelView + android:layout_width="300dp" + android:layout_height="wrap_content" + android:id="@+id/wheel"> + + </kankan.wheel.widget.WheelView> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml b/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml new file mode 100644 index 00000000..0d960c7e --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <View + android:id="@+id/bar" + android:layout_gravity="center_vertical" + android:layout_width="200dp" + android:layout_marginLeft="5dp" + android:layout_marginRight="5dp" + android:layout_height="1dp" + android:background="@color/black" /> + + <TextView + android:id="@+id/value" + android:layout_gravity="center_vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp" + android:textColor="@color/black" + android:textSize="20sp"/> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml index 06ffa233..22e01a20 100644 --- a/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml +++ b/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml @@ -23,7 +23,7 @@ android:paddingLeft="10dp" android:paddingRight="10dp"> - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -50,7 +50,7 @@ android:paddingRight="10dp" > - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -85,7 +85,7 @@ android:paddingRight="10dp" > - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" diff --git a/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml index 973639bc..2b67e67d 100644 --- a/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml +++ b/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml @@ -63,7 +63,7 @@ android:gravity="center" > - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" @@ -88,7 +88,7 @@ android:gravity="center" > - <ImageButton + <com.artifex.mupdf.android.ToolbarButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" diff --git a/platform/android/example/mupdf/src/main/res/values/strings.xml b/platform/android/example/mupdf/src/main/res/values/strings.xml index 3709da70..cc919a69 100644 --- a/platform/android/example/mupdf/src/main/res/values/strings.xml +++ b/platform/android/example/mupdf/src/main/res/values/strings.xml @@ -13,4 +13,7 @@ <string name="ok">OK</string> <string name="cancel">Cancel</string> + <string name="background">Background</string> + <string name="color">Color</string> + </resources> diff --git a/platform/android/example/mupdf/src/main/res/values/styles.xml b/platform/android/example/mupdf/src/main/res/values/styles.xml new file mode 100644 index 00000000..d767ff7f --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <!-- font color selection button. --> + <style name="font_color_button" parent="@android:style/Widget.Button"> + <item name="android:layout_height">20pt</item> + <item name="android:layout_width">20pt</item> + <item name="android:layout_margin">12dp</item> + </style> + +</resources> diff --git a/platform/java/src/kankan/wheel/widget/ItemsRange.java b/platform/java/src/kankan/wheel/widget/ItemsRange.java new file mode 100644 index 00000000..8b58a557 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/ItemsRange.java @@ -0,0 +1,81 @@ +/* + * Android Wheel Control. + * https://code.google.com/p/android-wheel/ + * + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +/** + * Range for visible items. + */ +public class ItemsRange { + // First item number + private int first; + + // Items count + private int count; + + /** + * Default constructor. Creates an empty range + */ + public ItemsRange() { + this(0, 0); + } + + /** + * Constructor + * @param first the number of first item + * @param count the count of items + */ + public ItemsRange(int first, int count) { + this.first = first; + this.count = count; + } + + /** + * Gets number of first item + * @return the number of the first item + */ + public int getFirst() { + return first; + } + + /** + * Gets number of last item + * @return the number of last item + */ + public int getLast() { + return getFirst() + getCount() - 1; + } + + /** + * Get items count + * @return the count of items + */ + public int getCount() { + return count; + } + + /** + * Tests whether item is contained by range + * @param index the item number + * @return true if item is contained + */ + public boolean contains(int index) { + return index >= getFirst() && index <= getLast(); + } +} diff --git a/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java b/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java new file mode 100644 index 00000000..baad897c --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java @@ -0,0 +1,33 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +/** + * Wheel changed listener interface. + * <p>The onChanged() method is called whenever current wheel positions is changed: + * <li> New Wheel position is set + * <li> Wheel view is scrolled + */ +public interface OnWheelChangedListener { + /** + * Callback method to be invoked when current item changed + * @param wheel the wheel view whose state has changed + * @param oldValue the old value of current item + * @param newValue the new value of current item + */ + void onChanged(WheelView wheel, int oldValue, int newValue); +} diff --git a/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java b/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java new file mode 100644 index 00000000..040bfc7b --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +/** + * Wheel clicked listener interface. + * <p>The onItemClicked() method is called whenever a wheel item is clicked + * <li> New Wheel position is set + * <li> Wheel view is scrolled + */ +public interface OnWheelClickedListener { + /** + * Callback method to be invoked when current item clicked + * @param wheel the wheel view + * @param itemIndex the index of clicked item + */ + void onItemClicked(WheelView wheel, int itemIndex); +} diff --git a/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java b/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java new file mode 100644 index 00000000..8571a4fe --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +/** + * Wheel scrolled listener interface. + */ +public interface OnWheelScrollListener { + /** + * Callback method to be invoked when scrolling started. + * @param wheel the wheel view whose state has changed. + */ + void onScrollingStarted(WheelView wheel); + + /** + * Callback method to be invoked when scrolling ended. + * @param wheel the wheel view whose state has changed. + */ + void onScrollingFinished(WheelView wheel); +} diff --git a/platform/java/src/kankan/wheel/widget/WheelAdapter.java b/platform/java/src/kankan/wheel/widget/WheelAdapter.java new file mode 100644 index 00000000..841c0b15 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/WheelAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +/** + * Wheel adapter interface + * + * @deprecated Use WheelViewAdapter + */ +public interface WheelAdapter { + /** + * Gets items count + * @return the count of wheel items + */ + public int getItemsCount(); + + /** + * Gets a wheel item by index. + * + * @param index the item index + * @return the wheel item text or null + */ + public String getItem(int index); + + /** + * Gets maximum item length. It is used to determine the wheel width. + * If -1 is returned there will be used the default wheel width. + * + * @return the maximum item length or -1 + */ + public int getMaximumLength(); +} diff --git a/platform/java/src/kankan/wheel/widget/WheelRecycle.java b/platform/java/src/kankan/wheel/widget/WheelRecycle.java new file mode 100644 index 00000000..242fc89e --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/WheelRecycle.java @@ -0,0 +1,153 @@ +/* + * Android Wheel Control. + * https://code.google.com/p/android-wheel/ + * + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +import android.view.View; +import android.widget.LinearLayout; + +import java.util.LinkedList; +import java.util.List; + +/** + * Recycle stores wheel items to reuse. + */ +public class WheelRecycle { + // Cached items + private List<View> items; + + // Cached empty items + private List<View> emptyItems; + + // Wheel view + private WheelView wheel; + + /** + * Constructor + * @param wheel the wheel view + */ + public WheelRecycle(WheelView wheel) { + this.wheel = wheel; + } + + /** + * Recycles items from specified layout. + * There are saved only items not included to specified range. + * All the cached items are removed from original layout. + * + * @param layout the layout containing items to be cached + * @param firstItem the number of first item in layout + * @param range the range of current wheel items + * @return the new value of first item number + */ + public int recycleItems(LinearLayout layout, int firstItem, ItemsRange range) { + int index = firstItem; + for (int i = 0; i < layout.getChildCount();) { + if (!range.contains(index)) { + recycleView(layout.getChildAt(i), index); + layout.removeViewAt(i); + if (i == 0) { // first item + firstItem++; + } + } else { + i++; // go to next item + } + index++; + } + return firstItem; + } + + /** + * Gets item view + * @return the cached view + */ + public View getItem() { + return getCachedView(items); + } + + /** + * Gets empty item view + * @return the cached empty view + */ + public View getEmptyItem() { + return getCachedView(emptyItems); + } + + /** + * Clears all views + */ + public void clearAll() { + if (items != null) { + items.clear(); + } + if (emptyItems != null) { + emptyItems.clear(); + } + } + + /** + * Adds view to specified cache. Creates a cache list if it is null. + * @param view the view to be cached + * @param cache the cache list + * @return the cache list + */ + private List<View> addView(View view, List<View> cache) { + if (cache == null) { + cache = new LinkedList<View>(); + } + + cache.add(view); + return cache; + } + + /** + * Adds view to cache. Determines view type (item view or empty one) by index. + * @param view the view to be cached + * @param index the index of view + */ + private void recycleView(View view, int index) { + int count = wheel.getViewAdapter().getItemsCount(); + + if ((index < 0 || index >= count) && !wheel.isCyclic()) { + // empty view + emptyItems = addView(view, emptyItems); + } else { + while (index < 0) { + index = count + index; + } + index %= count; + items = addView(view, items); + } + } + + /** + * Gets view from specified cache. + * @param cache the cache + * @return the first view from cache. + */ + private View getCachedView(List<View> cache) { + if (cache != null && cache.size() > 0) { + View view = cache.get(0); + cache.remove(0); + return view; + } + return null; + } + +} diff --git a/platform/java/src/kankan/wheel/widget/WheelScroller.java b/platform/java/src/kankan/wheel/widget/WheelScroller.java new file mode 100644 index 00000000..0372da1a --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/WheelScroller.java @@ -0,0 +1,252 @@ +/* + * Android Wheel Control. + * https://code.google.com/p/android-wheel/ + * + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +/** + * Scroller class handles scrolling events and updates the + */ +public class WheelScroller { + /** + * Scrolling listener interface + */ + public interface ScrollingListener { + /** + * Scrolling callback called when scrolling is performed. + * @param distance the distance to scroll + */ + void onScroll(int distance); + + /** + * Starting callback called when scrolling is started + */ + void onStarted(); + + /** + * Finishing callback called after justifying + */ + void onFinished(); + + /** + * Justifying callback called to justify a view when scrolling is ended + */ + void onJustify(); + } + + /** Scrolling duration */ + private static final int SCROLLING_DURATION = 400; + + /** Minimum delta for scrolling */ + public static final int MIN_DELTA_FOR_SCROLLING = 1; + + // Listener + private ScrollingListener listener; + + // Context + private Context context; + + // Scrolling + private GestureDetector gestureDetector; + private Scroller scroller; + private int lastScrollY; + private float lastTouchedY; + private boolean isScrollingPerformed; + + /** + * Constructor + * @param context the current context + * @param listener the scrolling listener + */ + public WheelScroller(Context context, ScrollingListener listener) { + gestureDetector = new GestureDetector(context, gestureListener); + gestureDetector.setIsLongpressEnabled(false); + + scroller = new Scroller(context); + + this.listener = listener; + this.context = context; + } + + /** + * Set the the specified scrolling interpolator + * @param interpolator the interpolator + */ + public void setInterpolator(Interpolator interpolator) { + scroller.forceFinished(true); + scroller = new Scroller(context, interpolator); + } + + /** + * Scroll the wheel + * @param distance the scrolling distance + * @param time the scrolling duration + */ + public void scroll(int distance, int time) { + scroller.forceFinished(true); + + lastScrollY = 0; + + scroller.startScroll(0, 0, 0, distance, time != 0 ? time : SCROLLING_DURATION); + setNextMessage(MESSAGE_SCROLL); + + startScrolling(); + } + + /** + * Stops scrolling + */ + public void stopScrolling() { + scroller.forceFinished(true); + } + + /** + * Handles Touch event + * @param event the motion event + * @return + */ + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastTouchedY = event.getY(); + scroller.forceFinished(true); + clearMessages(); + break; + + case MotionEvent.ACTION_MOVE: + // perform scrolling + int distanceY = (int)(event.getY() - lastTouchedY); + if (distanceY != 0) { + startScrolling(); + listener.onScroll(distanceY); + lastTouchedY = event.getY(); + } + break; + } + + if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) { + justify(); + } + + return true; + } + + // gesture listener + private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() { + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // Do scrolling in onTouchEvent() since onScroll() are not call immediately + // when user touch and move the wheel + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + lastScrollY = 0; + final int maxY = 0x7FFFFFFF; + final int minY = -maxY; + scroller.fling(0, lastScrollY, 0, (int) -velocityY, 0, 0, minY, maxY); + setNextMessage(MESSAGE_SCROLL); + return true; + } + }; + + // Messages + private final int MESSAGE_SCROLL = 0; + private final int MESSAGE_JUSTIFY = 1; + + /** + * Set next message to queue. Clears queue before. + * + * @param message the message to set + */ + private void setNextMessage(int message) { + clearMessages(); + animationHandler.sendEmptyMessage(message); + } + + /** + * Clears messages from queue + */ + private void clearMessages() { + animationHandler.removeMessages(MESSAGE_SCROLL); + animationHandler.removeMessages(MESSAGE_JUSTIFY); + } + + // animation handler + private Handler animationHandler = new Handler() { + public void handleMessage(Message msg) { + scroller.computeScrollOffset(); + int currY = scroller.getCurrY(); + int delta = lastScrollY - currY; + lastScrollY = currY; + if (delta != 0) { + listener.onScroll(delta); + } + + // scrolling is not finished when it comes to final Y + // so, finish it manually + if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) { + currY = scroller.getFinalY(); + scroller.forceFinished(true); + } + if (!scroller.isFinished()) { + animationHandler.sendEmptyMessage(msg.what); + } else if (msg.what == MESSAGE_SCROLL) { + justify(); + } else { + finishScrolling(); + } + } + }; + + /** + * Justifies wheel + */ + private void justify() { + listener.onJustify(); + setNextMessage(MESSAGE_JUSTIFY); + } + + /** + * Starts scrolling + */ + private void startScrolling() { + if (!isScrollingPerformed) { + isScrollingPerformed = true; + listener.onStarted(); + } + } + + /** + * Finishes scrolling + */ + void finishScrolling() { + if (isScrollingPerformed) { + listener.onFinished(); + isScrollingPerformed = false; + } + } +} diff --git a/platform/java/src/kankan/wheel/widget/WheelView.java b/platform/java/src/kankan/wheel/widget/WheelView.java new file mode 100644 index 00000000..e8324e88 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/WheelView.java @@ -0,0 +1,889 @@ +/* + * Android Wheel Control. + * https://code.google.com/p/android-wheel/ + * + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.GradientDrawable.Orientation; +import android.media.AudioManager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.animation.Interpolator; +import android.widget.LinearLayout; + +import com.artifex.mupdf.fitz.R; + +import java.util.LinkedList; +import java.util.List; + +import kankan.wheel.widget.adapters.WheelViewAdapter; + + + +/** + * Numeric wheel view. + * + * @author Yuri Kanivets + */ +public class WheelView extends View { + + /** Top and bottom shadows colors */ + private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111, + 0x00AAAAAA, 0x00AAAAAA }; + + /** Top and bottom items offset (to hide that) */ + private static final int ITEM_OFFSET_PERCENT = 10; + + /** Left and right padding value */ + private static final int PADDING = 10; + + /** Default count of visible items */ + private static final int DEF_VISIBLE_ITEMS = 5; + + // Wheel Values + private int currentItem = 0; + + // Count of visible items + private int visibleItems = DEF_VISIBLE_ITEMS; + + // Item height + private int itemHeight = 0; + + // Center Line + private Drawable centerDrawable; + + // // Shadows drawables + private GradientDrawable topShadow; + private GradientDrawable bottomShadow; + + // Scrolling + private WheelScroller scroller; + private boolean isScrollingPerformed; + private int scrollingOffset; + + // Cyclic + boolean isCyclic = false; + + // Items layout + private LinearLayout itemsLayout; + + // The number of first item in layout + private int firstItem; + + // View adapter + private WheelViewAdapter viewAdapter; + + // Recycle + private WheelRecycle recycle = new WheelRecycle(this); + + // Listeners + private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>(); + private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>(); + private List<OnWheelClickedListener> clickingListeners = new LinkedList<OnWheelClickedListener>(); + + /** + * Constructor + */ + public WheelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initData(context); + } + + /** + * Constructor + */ + public WheelView(Context context, AttributeSet attrs) { + super(context, attrs); + initData(context); + } + + /** + * Constructor + */ + public WheelView(Context context) { + super(context); + initData(context); + } + + /** + * Initializes class data + * @param context the context + */ + private void initData(Context context) { + scroller = new WheelScroller(getContext(), scrollingListener); + } + + // Scrolling listener + WheelScroller.ScrollingListener scrollingListener = new WheelScroller.ScrollingListener() { + public void onStarted() { + isScrollingPerformed = true; + notifyScrollingListenersAboutStart(); + } + + public void onScroll(int distance) { + doScroll(distance); + + int height = getHeight(); + if (scrollingOffset > height) { + scrollingOffset = height; + scroller.stopScrolling(); + } else if (scrollingOffset < -height) { + scrollingOffset = -height; + scroller.stopScrolling(); + } + } + + public void onFinished() { + if (isScrollingPerformed) { + notifyScrollingListenersAboutEnd(); + isScrollingPerformed = false; + } + + scrollingOffset = 0; + invalidate(); + } + + public void onJustify() { + if (Math.abs(scrollingOffset) > WheelScroller.MIN_DELTA_FOR_SCROLLING) { + scroller.scroll(scrollingOffset, 0); + } + } + }; + + /** + * Set the the specified scrolling interpolator + * @param interpolator the interpolator + */ + public void setInterpolator(Interpolator interpolator) { + scroller.setInterpolator(interpolator); + } + + /** + * Gets count of visible items + * + * @return the count of visible items + */ + public int getVisibleItems() { + return visibleItems; + } + + /** + * Sets the desired count of visible items. + * Actual amount of visible items depends on wheel layout parameters. + * To apply changes and rebuild view call measure(). + * + * @param count the desired count for visible items + */ + public void setVisibleItems(int count) { + visibleItems = count; + } + + /** + * Gets view adapter + * @return the view adapter + */ + public WheelViewAdapter getViewAdapter() { + return viewAdapter; + } + + // Adapter listener + private DataSetObserver dataObserver = new DataSetObserver() { + @Override + public void onChanged() { + invalidateWheel(false); + } + + @Override + public void onInvalidated() { + invalidateWheel(true); + } + }; + + /** + * Sets view adapter. Usually new adapters contain different views, so + * it needs to rebuild view by calling measure(). + * + * @param viewAdapter the view adapter + */ + public void setViewAdapter(WheelViewAdapter viewAdapter) { + if (this.viewAdapter != null) { + this.viewAdapter.unregisterDataSetObserver(dataObserver); + } + this.viewAdapter = viewAdapter; + if (this.viewAdapter != null) { + this.viewAdapter.registerDataSetObserver(dataObserver); + } + + invalidateWheel(true); + } + + /** + * Adds wheel changing listener + * @param listener the listener + */ + public void addChangingListener(OnWheelChangedListener listener) { + changingListeners.add(listener); + } + + /** + * Removes wheel changing listener + * @param listener the listener + */ + public void removeChangingListener(OnWheelChangedListener listener) { + changingListeners.remove(listener); + } + + /** + * Notifies changing listeners + * @param oldValue the old wheel value + * @param newValue the new wheel value + */ + protected void notifyChangingListeners(int oldValue, int newValue) { + for (OnWheelChangedListener listener : changingListeners) { + listener.onChanged(this, oldValue, newValue); + } + + // play a sound. + // TODO: something better + this.playSoundEffect(SoundEffectConstants.CLICK); + } + + /** + * Adds wheel scrolling listener + * @param listener the listener + */ + public void addScrollingListener(OnWheelScrollListener listener) { + scrollingListeners.add(listener); + } + + /** + * Removes wheel scrolling listener + * @param listener the listener + */ + public void removeScrollingListener(OnWheelScrollListener listener) { + scrollingListeners.remove(listener); + } + + /** + * Notifies listeners about starting scrolling + */ + protected void notifyScrollingListenersAboutStart() { + for (OnWheelScrollListener listener : scrollingListeners) { + listener.onScrollingStarted(this); + } + } + + /** + * Notifies listeners about ending scrolling + */ + protected void notifyScrollingListenersAboutEnd() { + for (OnWheelScrollListener listener : scrollingListeners) { + listener.onScrollingFinished(this); + } + } + + /** + * Adds wheel clicking listener + * @param listener the listener + */ + public void addClickingListener(OnWheelClickedListener listener) { + clickingListeners.add(listener); + } + + /** + * Removes wheel clicking listener + * @param listener the listener + */ + public void removeClickingListener(OnWheelClickedListener listener) { + clickingListeners.remove(listener); + } + + /** + * Notifies listeners about clicking + */ + protected void notifyClickListenersAboutClick(int item) { + for (OnWheelClickedListener listener : clickingListeners) { + listener.onItemClicked(this, item); + } + } + + /** + * Gets current value + * + * @return the current value + */ + public int getCurrentItem() { + return currentItem; + } + + /** + * Sets the current item. Does nothing when index is wrong. + * + * @param index the item index + * @param animated the animation flag + */ + public void setCurrentItem(int index, boolean animated) { + if (viewAdapter == null || viewAdapter.getItemsCount() == 0) { + return; // throw? + } + + int itemCount = viewAdapter.getItemsCount(); + if (index < 0 || index >= itemCount) { + if (isCyclic) { + while (index < 0) { + index += itemCount; + } + index %= itemCount; + } else{ + return; // throw? + } + } + if (index != currentItem) { + if (animated) { + int itemsToScroll = index - currentItem; + if (isCyclic) { + int scroll = itemCount + Math.min(index, currentItem) - Math.max(index, currentItem); + if (scroll < Math.abs(itemsToScroll)) { + itemsToScroll = itemsToScroll < 0 ? scroll : -scroll; + } + } + scroll(itemsToScroll, 0); + } else { + scrollingOffset = 0; + + int old = currentItem; + currentItem = index; + + notifyChangingListeners(old, currentItem); + + invalidate(); + } + } + } + + /** + * Sets the current item w/o animation. Does nothing when index is wrong. + * + * @param index the item index + */ + public void setCurrentItem(int index) { + setCurrentItem(index, false); + } + + /** + * Tests if wheel is cyclic. That means before the 1st item there is shown the last one + * @return true if wheel is cyclic + */ + public boolean isCyclic() { + return isCyclic; + } + + /** + * Set wheel cyclic flag + * @param isCyclic the flag to set + */ + public void setCyclic(boolean isCyclic) { + this.isCyclic = isCyclic; + invalidateWheel(false); + } + + /** + * Invalidates wheel + * @param clearCaches if true then cached views will be clear + */ + public void invalidateWheel(boolean clearCaches) { + if (clearCaches) { + recycle.clearAll(); + if (itemsLayout != null) { + itemsLayout.removeAllViews(); + } + scrollingOffset = 0; + } else if (itemsLayout != null) { + // cache all items + recycle.recycleItems(itemsLayout, firstItem, new ItemsRange()); + } + + invalidate(); + } + + /** + * Initializes resources + */ + private void initResourcesIfNecessary() { + if (centerDrawable == null) { + centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val); + } + + if (topShadow == null) { + topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS); + } + + if (bottomShadow == null) { + bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS); + } + + setBackgroundResource(R.drawable.wheel_bg); + } + + /** + * Calculates desired height for layout + * + * @param layout + * the source layout + * @return the desired layout height + */ + private int getDesiredHeight(LinearLayout layout) { + if (layout != null && layout.getChildAt(0) != null) { + itemHeight = layout.getChildAt(0).getMeasuredHeight(); + } + + int desired = itemHeight * visibleItems - itemHeight * ITEM_OFFSET_PERCENT / 50; + + return Math.max(desired, getSuggestedMinimumHeight()); + } + + /** + * Returns height of wheel item + * @return the item height + */ + private int getItemHeight() { + if (itemHeight != 0) { + return itemHeight; + } + + if (itemsLayout != null && itemsLayout.getChildAt(0) != null) { + itemHeight = itemsLayout.getChildAt(0).getHeight(); + return itemHeight; + } + + return getHeight() / visibleItems; + } + + /** + * Calculates control width and creates text layouts + * @param widthSize the input layout width + * @param mode the layout mode + * @return the calculated control width + */ + private int calculateLayoutWidth(int widthSize, int mode) { + initResourcesIfNecessary(); + + // TODO: make it static + itemsLayout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + itemsLayout.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int width = itemsLayout.getMeasuredWidth(); + + if (mode == MeasureSpec.EXACTLY) { + width = widthSize; + } else { + width += 2 * PADDING; + + // Check against our minimum width + width = Math.max(width, getSuggestedMinimumWidth()); + + if (mode == MeasureSpec.AT_MOST && widthSize < width) { + width = widthSize; + } + } + + itemsLayout.measure(MeasureSpec.makeMeasureSpec(width - 2 * PADDING, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + + return width; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + buildViewForMeasuring(); + + int width = calculateLayoutWidth(widthSize, widthMode); + + int height; + if (heightMode == MeasureSpec.EXACTLY) { + height = heightSize; + } else { + height = getDesiredHeight(itemsLayout); + + if (heightMode == MeasureSpec.AT_MOST) { + height = Math.min(height, heightSize); + } + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + layout(r - l, b - t); + } + + /** + * Sets layouts width and height + * @param width the layout width + * @param height the layout height + */ + private void layout(int width, int height) { + int itemsWidth = width - 2 * PADDING; + + itemsLayout.layout(0, 0, itemsWidth, height); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (viewAdapter != null && viewAdapter.getItemsCount() > 0) { + updateView(); + + drawItems(canvas); + drawCenterRect(canvas); + } + +// drawShadows(canvas); + } + + /** + * Draws shadows on top and bottom of control + * @param canvas the canvas for drawing + */ + private void drawShadows(Canvas canvas) { + int height = (int)(1.5 * getItemHeight()); + topShadow.setBounds(0, 0, getWidth(), height); + topShadow.draw(canvas); + + bottomShadow.setBounds(0, getHeight() - height, getWidth(), getHeight()); + bottomShadow.draw(canvas); + } + + /** + * Draws items + * @param canvas the canvas for drawing + */ + private void drawItems(Canvas canvas) { + canvas.save(); + + int top = (currentItem - firstItem) * getItemHeight() + (getItemHeight() - getHeight()) / 2; + canvas.translate(PADDING, - top + scrollingOffset); + + itemsLayout.draw(canvas); + + canvas.restore(); + } + + /** + * Draws rect for current value + * @param canvas the canvas for drawing + */ + private void drawCenterRect(Canvas canvas) { + int center = getHeight() / 2; + int offset = (int) (getItemHeight() / 2 * 1.2); + centerDrawable.setBounds(0, center - offset, getWidth(), center + offset); + centerDrawable.draw(canvas); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled() || getViewAdapter() == null) { + return true; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + break; + + case MotionEvent.ACTION_UP: + if (!isScrollingPerformed) { + int distance = (int) event.getY() - getHeight() / 2; + if (distance > 0) { + distance += getItemHeight() / 2; + } else { + distance -= getItemHeight() / 2; + } + int items = distance / getItemHeight(); + if (items != 0 && isValidItemIndex(currentItem + items)) { + notifyClickListenersAboutClick(currentItem + items); + } + } + break; + } + + return scroller.onTouchEvent(event); + } + + /** + * Scrolls the wheel + * @param delta the scrolling value + */ + private void doScroll(int delta) { + scrollingOffset += delta; + + int itemHeight = getItemHeight(); + int count = scrollingOffset / itemHeight; + + int pos = currentItem - count; + int itemCount = viewAdapter.getItemsCount(); + + int fixPos = scrollingOffset % itemHeight; + if (Math.abs(fixPos) <= itemHeight / 2) { + fixPos = 0; + } + if (isCyclic && itemCount > 0) { + if (fixPos > 0) { + pos--; + count++; + } else if (fixPos < 0) { + pos++; + count--; + } + // fix position by rotating + while (pos < 0) { + pos += itemCount; + } + pos %= itemCount; + } else { + // + if (pos < 0) { + count = currentItem; + pos = 0; + } else if (pos >= itemCount) { + count = currentItem - itemCount + 1; + pos = itemCount - 1; + } else if (pos > 0 && fixPos > 0) { + pos--; + count++; + } else if (pos < itemCount - 1 && fixPos < 0) { + pos++; + count--; + } + } + + int offset = scrollingOffset; + if (pos != currentItem) { + setCurrentItem(pos, false); + } else { + invalidate(); + } + + // update offset + scrollingOffset = offset - count * itemHeight; + if (scrollingOffset > getHeight()) { + scrollingOffset = scrollingOffset % getHeight() + getHeight(); + } + } + + /** + * Scroll the wheel + * @param itemsToSkip items to scroll + * @param time scrolling duration + */ + public void scroll(int itemsToScroll, int time) { + int distance = itemsToScroll * getItemHeight() - scrollingOffset; + scroller.scroll(distance, time); + } + + /** + * Calculates range for wheel items + * @return the items range + */ + private ItemsRange getItemsRange() { + if (getItemHeight() == 0) { + return null; + } + + int first = currentItem; + int count = 1; + + while (count * getItemHeight() < getHeight()) { + first--; + count += 2; // top + bottom items + } + + if (scrollingOffset != 0) { + if (scrollingOffset > 0) { + first--; + } + count++; + + // process empty items above the first or below the second + int emptyItems = scrollingOffset / getItemHeight(); + first -= emptyItems; + count += Math.asin(emptyItems); + } + return new ItemsRange(first, count); + } + + /** + * Rebuilds wheel items if necessary. Caches all unused items. + * + * @return true if items are rebuilt + */ + private boolean rebuildItems() { + boolean updated = false; + ItemsRange range = getItemsRange(); + if (itemsLayout != null) { + int first = recycle.recycleItems(itemsLayout, firstItem, range); + updated = firstItem != first; + firstItem = first; + } else { + createItemsLayout(); + updated = true; + } + + if (!updated) { + updated = firstItem != range.getFirst() || itemsLayout.getChildCount() != range.getCount(); + } + + if (firstItem > range.getFirst() && firstItem <= range.getLast()) { + for (int i = firstItem - 1; i >= range.getFirst(); i--) { + if (!addViewItem(i, true)) { + break; + } + firstItem = i; + } + } else { + firstItem = range.getFirst(); + } + + int first = firstItem; + for (int i = itemsLayout.getChildCount(); i < range.getCount(); i++) { + if (!addViewItem(firstItem + i, false) && itemsLayout.getChildCount() == 0) { + first++; + } + } + firstItem = first; + + return updated; + } + + /** + * Updates view. Rebuilds items and label if necessary, recalculate items sizes. + */ + private void updateView() { + if (rebuildItems()) { + calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY); + layout(getWidth(), getHeight()); + } + } + + /** + * Creates item layouts if necessary + */ + private void createItemsLayout() { + if (itemsLayout == null) { + itemsLayout = new LinearLayout(getContext()); + itemsLayout.setOrientation(LinearLayout.VERTICAL); + } + } + + /** + * Builds view for measuring + */ + private void buildViewForMeasuring() { + // clear all items + if (itemsLayout != null) { + recycle.recycleItems(itemsLayout, firstItem, new ItemsRange()); + } else { + createItemsLayout(); + } + + // add views + int addItems = visibleItems / 2; + for (int i = currentItem + addItems; i >= currentItem - addItems; i--) { + if (addViewItem(i, true)) { + firstItem = i; + } + } + } + + /** + * Adds view for item to items layout + * @param index the item index + * @param first the flag indicates if view should be first + * @return true if corresponding item exists and is added + */ + private boolean addViewItem(int index, boolean first) { + View view = getItemView(index); + if (view != null) { + if (first) { + itemsLayout.addView(view, 0); + } else { + itemsLayout.addView(view); + } + + return true; + } + + return false; + } + + /** + * Checks whether intem index is valid + * @param index the item index + * @return true if item index is not out of bounds or the wheel is cyclic + */ + private boolean isValidItemIndex(int index) { + return viewAdapter != null && viewAdapter.getItemsCount() > 0 && + (isCyclic || index >= 0 && index < viewAdapter.getItemsCount()); + } + + /** + * Returns view for specified item + * @param index the item index + * @return item view or empty view if index is out of bounds + */ + private View getItemView(int index) { + if (viewAdapter == null || viewAdapter.getItemsCount() == 0) { + return null; + } + int count = viewAdapter.getItemsCount(); + if (!isValidItemIndex(index)) { + return viewAdapter.getEmptyItem(recycle.getEmptyItem(), itemsLayout); + } else { + while (index < 0) { + index = count + index; + } + } + + index %= count; + return viewAdapter.getItem(index, recycle.getItem(), itemsLayout); + } + + /** + * Stops scrolling + */ + public void stopScrolling() { + scroller.stopScrolling(); + } +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java new file mode 100644 index 00000000..8d7f1447 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget.adapters; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; + +import java.util.LinkedList; +import java.util.List; + +/** + * Abstract Wheel adapter. + */ +public abstract class AbstractWheelAdapter implements WheelViewAdapter { + // Observers + private List<DataSetObserver> datasetObservers; + + @Override + public View getEmptyItem(View convertView, ViewGroup parent) { + return null; + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + if (datasetObservers == null) { + datasetObservers = new LinkedList<DataSetObserver>(); + } + datasetObservers.add(observer); + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + if (datasetObservers != null) { + datasetObservers.remove(observer); + } + } + + /** + * Notifies observers about data changing + */ + protected void notifyDataChangedEvent() { + if (datasetObservers != null) { + for (DataSetObserver observer : datasetObservers) { + observer.onChanged(); + } + } + } + + /** + * Notifies observers about invalidating data + */ + protected void notifyDataInvalidatedEvent() { + if (datasetObservers != null) { + for (DataSetObserver observer : datasetObservers) { + observer.onInvalidated(); + } + } + } +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java new file mode 100644 index 00000000..620c8e74 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java @@ -0,0 +1,268 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package kankan.wheel.widget.adapters; + +import android.content.Context; +import android.graphics.Typeface; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * Abstract wheel adapter provides common functionality for adapters. + */ +public abstract class AbstractWheelTextAdapter extends AbstractWheelAdapter { + + /** Text view resource. Used as a default view for adapter. */ + public static final int TEXT_VIEW_ITEM_RESOURCE = -1; + + /** No resource constant. */ + protected static final int NO_RESOURCE = 0; + + /** Default text color */ + public static final int DEFAULT_TEXT_COLOR = 0xFF101010; + + /** Default text color */ + public static final int LABEL_COLOR = 0xFF700070; + + /** Default text size */ + public static final int DEFAULT_TEXT_SIZE = 24; + + // Text settings + private int textColor = DEFAULT_TEXT_COLOR; + private int textSize = DEFAULT_TEXT_SIZE; + + // Current context + protected Context context; + // Layout inflater + protected LayoutInflater inflater; + + // Items resources + protected int itemResourceId; + protected int itemTextResourceId; + + // Empty items resources + protected int emptyItemResourceId; + + /** + * Constructor + * @param context the current context + */ + protected AbstractWheelTextAdapter(Context context) { + this(context, TEXT_VIEW_ITEM_RESOURCE); + } + + /** + * Constructor + * @param context the current context + * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views + */ + protected AbstractWheelTextAdapter(Context context, int itemResource) { + this(context, itemResource, NO_RESOURCE); + } + + /** + * Constructor + * @param context the current context + * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views + * @param itemTextResource the resource ID for a text view in the item layout + */ + protected AbstractWheelTextAdapter(Context context, int itemResource, int itemTextResource) { + this.context = context; + itemResourceId = itemResource; + itemTextResourceId = itemTextResource; + + inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + /** + * Gets text color + * @return the text color + */ + public int getTextColor() { + return textColor; + } + + /** + * Sets text color + * @param textColor the text color to set + */ + public void setTextColor(int textColor) { + this.textColor = textColor; + } + + /** + * Gets text size + * @return the text size + */ + public int getTextSize() { + return textSize; + } + + /** + * Sets text size + * @param textSize the text size to set + */ + public void setTextSize(int textSize) { + this.textSize = textSize; + } + + /** + * Gets resource Id for items views + * @return the item resource Id + */ + public int getItemResource() { + return itemResourceId; + } + + /** + * Sets resource Id for items views + * @param itemResourceId the resource Id to set + */ + public void setItemResource(int itemResourceId) { + this.itemResourceId = itemResourceId; + } + + /** + * Gets resource Id for text view in item layout + * @return the item text resource Id + */ + public int getItemTextResource() { + return itemTextResourceId; + } + + /** + * Sets resource Id for text view in item layout + * @param itemTextResourceId the item text resource Id to set + */ + public void setItemTextResource(int itemTextResourceId) { + this.itemTextResourceId = itemTextResourceId; + } + + /** + * Gets resource Id for empty items views + * @return the empty item resource Id + */ + public int getEmptyItemResource() { + return emptyItemResourceId; + } + + /** + * Sets resource Id for empty items views + * @param emptyItemResourceId the empty item resource Id to set + */ + public void setEmptyItemResource(int emptyItemResourceId) { + this.emptyItemResourceId = emptyItemResourceId; + } + + + /** + * Returns text for specified item + * @param index the item index + * @return the text of specified items + */ + protected abstract CharSequence getItemText(int index); + + @Override + public View getItem(int index, View convertView, ViewGroup parent) { + if (index >= 0 && index < getItemsCount()) { + if (convertView == null) { + convertView = getView(itemResourceId, parent); + } + TextView textView = getTextView(convertView, itemTextResourceId); + if (textView != null) { + CharSequence text = getItemText(index); + if (text == null) { + text = ""; + } + textView.setText(text); + + if (itemResourceId == TEXT_VIEW_ITEM_RESOURCE) { + configureTextView(textView); + } + } + return convertView; + } + return null; + } + + @Override + public View getEmptyItem(View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getView(emptyItemResourceId, parent); + } + if (emptyItemResourceId == TEXT_VIEW_ITEM_RESOURCE && convertView instanceof TextView) { + configureTextView((TextView)convertView); + } + + return convertView; + } + + /** + * Configures text view. Is called for the TEXT_VIEW_ITEM_RESOURCE views. + * @param view the text view to be configured + */ + protected void configureTextView(TextView view) { + view.setTextColor(textColor); + view.setGravity(Gravity.CENTER); + view.setTextSize(textSize); + view.setLines(1); +// view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); + view.setTypeface(Typeface.SANS_SERIF, Typeface.NORMAL); + } + + /** + * Loads a text view from view + * @param view the text view or layout containing it + * @param textResource the text resource Id in layout + * @return the loaded text view + */ + private TextView getTextView(View view, int textResource) { + TextView text = null; + try { + if (textResource == NO_RESOURCE && view instanceof TextView) { + text = (TextView) view; + } else if (textResource != NO_RESOURCE) { + text = (TextView) view.findViewById(textResource); + } + } catch (ClassCastException e) { + Log.e("AbstractWheelAdapter", "You must supply a resource ID for a TextView"); + throw new IllegalStateException( + "AbstractWheelAdapter requires the resource ID to be a TextView", e); + } + + return text; + } + + /** + * Loads view from resources + * @param resource the resource Id + * @return the loaded view or null if resource is not set + */ + private View getView(int resource, ViewGroup parent) { + switch (resource) { + case NO_RESOURCE: + return null; + case TEXT_VIEW_ITEM_RESOURCE: + return new TextView(context); + default: + return inflater.inflate(resource, parent, false); + } + } +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java b/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java new file mode 100644 index 00000000..000001cd --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget.adapters; + +import android.content.Context; + +import kankan.wheel.widget.WheelAdapter; + +/** + * Adapter class for old wheel adapter (deprecated WheelAdapter class). + * + * @deprecated Will be removed soon + */ +public class AdapterWheel extends AbstractWheelTextAdapter { + + // Source adapter + private WheelAdapter adapter; + + /** + * Constructor + * @param context the current context + * @param adapter the source adapter + */ + public AdapterWheel(Context context, WheelAdapter adapter) { + super(context); + + this.adapter = adapter; + } + + /** + * Gets original adapter + * @return the original adapter + */ + public WheelAdapter getAdapter() { + return adapter; + } + + @Override + public int getItemsCount() { + return adapter.getItemsCount(); + } + + @Override + protected CharSequence getItemText(int index) { + return adapter.getItem(index); + } + +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java new file mode 100644 index 00000000..c9d430d7 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package kankan.wheel.widget.adapters; + +import android.content.Context; + +/** + * The simple Array wheel adapter + * @param <T> the element type + */ +public class ArrayWheelAdapter<T> extends AbstractWheelTextAdapter { + + // items + private T items[]; + + /** + * Constructor + * @param context the current context + * @param items the items + */ + public ArrayWheelAdapter(Context context, T items[]) { + super(context); + + //setEmptyItemResource(TEXT_VIEW_ITEM_RESOURCE); + this.items = items; + } + + public void setItems(T items[]) + { + this.items = items; + } + + @Override + public CharSequence getItemText(int index) { + if (index >= 0 && index < items.length) { + T item = items[index]; + if (item instanceof CharSequence) { + return (CharSequence) item; + } + return item.toString(); + } + return null; + } + + @Override + public int getItemsCount() { + return items.length; + } +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java new file mode 100644 index 00000000..cf4a9954 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java @@ -0,0 +1,50 @@ +package kankan.wheel.widget.adapters; + +import android.content.Context; +import android.graphics.Color; +import android.widget.TextView; + +public class ArrayWheelAdapterColor<T> extends AbstractWheelTextAdapter { + + // items + private T items[]; + + public ArrayWheelAdapterColor(Context context, T items[]) { + super(context); + + this.items = items; + } + + @Override + public CharSequence getItemText(int index) { + if (index >= 0 && index < items.length) { + T item = items[index]; + if (item instanceof CharSequence) { + return (CharSequence) item; + } + return item.toString(); + } + return null; + } + + @Override + public int getItemsCount() { + return items.length; + } + + @Override + protected void configureTextView(TextView view) { + super.configureTextView(view); + + // if the text ends with "(red)" + // color it red. + String text = view.getText().toString(); + if (text.endsWith("(red)")) + { + text = text.replace("(red)",""); + view.setText(text); + view.setTextColor(Color.parseColor("#ff0000")); + } + } + +}
\ No newline at end of file diff --git a/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java new file mode 100644 index 00000000..ec8b9c86 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget.adapters; + +import android.content.Context; + +/** + * Numeric Wheel adapter. + */ +public class NumericWheelAdapter extends AbstractWheelTextAdapter { + + /** The default min value */ + public static final int DEFAULT_MAX_VALUE = 9; + + /** The default max value */ + private static final int DEFAULT_MIN_VALUE = 0; + + // Values + private int minValue; + private int maxValue; + + // format + private String format; + + /** + * Constructor + * @param context the current context + */ + public NumericWheelAdapter(Context context) { + this(context, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE); + } + + /** + * Constructor + * @param context the current context + * @param minValue the wheel min value + * @param maxValue the wheel max value + */ + public NumericWheelAdapter(Context context, int minValue, int maxValue) { + this(context, minValue, maxValue, null); + } + + /** + * Constructor + * @param context the current context + * @param minValue the wheel min value + * @param maxValue the wheel max value + * @param format the format string + */ + public NumericWheelAdapter(Context context, int minValue, int maxValue, String format) { + super(context); + + this.minValue = minValue; + this.maxValue = maxValue; + this.format = format; + } + + @Override + public CharSequence getItemText(int index) { + if (index >= 0 && index < getItemsCount()) { + int value = minValue + index; + return format != null ? String.format(format, value) : Integer.toString(value); + } + return null; + } + + @Override + public int getItemsCount() { + return maxValue - minValue + 1; + } +} diff --git a/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java new file mode 100644 index 00000000..db256570 --- /dev/null +++ b/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2011 Yuri Kanivets + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kankan.wheel.widget.adapters; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; + +/** + * Wheel items adapter interface + */ +public interface WheelViewAdapter { + /** + * Gets items count + * @return the count of wheel items + */ + public int getItemsCount(); + + /** + * Get a View that displays the data at the specified position in the data set + * + * @param index the item index + * @param convertView the old view to reuse if possible + * @param parent the parent that this view will eventually be attached to + * @return the wheel item View + */ + public View getItem(int index, View convertView, ViewGroup parent); + + /** + * Get a View that displays an empty wheel item placed before the first or after + * the last wheel item. + * + * @param convertView the old view to reuse if possible + * @param parent the parent that this view will eventually be attached to + * @return the empty item View + */ + public View getEmptyItem(View convertView, ViewGroup parent); + + /** + * Register an observer that is called when changes happen to the data used by this adapter. + * @param observer the observer to be registered + */ + public void registerDataSetObserver(DataSetObserver observer); + + /** + * Unregister an observer that has previously been registered + * @param observer the observer to be unregistered + */ + void unregisterDataSetObserver (DataSetObserver observer); +} |