--

Monday, 26 December 2016

SVG Line drawing

If you are looking for an Animated line drawing in sequence, you are at the right place. We have to download SVG Android library (i.e. Add jar to lib folder svg-android-1.1.jar) to use SVG image in application.




Working Logic

I elucidate the working logic step by step. We have to load the svg from the resources and render the svg to canvas and catch all the paths while rendering.




public List<SvgPath> getPathsForViewport(final int width, final int height) {
    final float strokeWidth = mSourcePaint.getStrokeWidth();
    Canvas canvas = new Canvas() {
        private final Matrix mMatrix = new Matrix();

        @Override        public int getWidth() {
            return width;
        }

        @Override        public int getHeight() {
            return height;
        }

        @Override        public void drawPath(Path path, Paint paint) {
            Path dst = new Path();

            //noinspection deprecation            getMatrix(mMatrix);
            path.transform(mMatrix, dst);
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(strokeWidth);
            mPaths.add(new SvgPath(dst, paint));
        }
    };

    rescaleCanvas(width, height, strokeWidth, canvas);

    return mPaths;
} 
then rescale the canvas with specific width and height.

private void rescaleCanvas(int width, int height, float strokeWidth, Canvas canvas) {
    if (mSvg == null) 
        return;
    final RectF viewBox = mSvg.getDocumentViewBox();

    final float scale = Math.min(width
                    / (viewBox.width() + strokeWidth),
            height / (viewBox.height() + strokeWidth));

    canvas.translate((width - viewBox.width() * scale) / 2.0f,
            (height - viewBox.height() * scale) / 2.0f);
    canvas.scale(scale, scale);

    mSvg.renderToCanvas(canvas);
} 

The line path is drawn on Canvas at onDraw() method. We pick either natural SVG image 
color or custom color. 
  
 
@Overrideprotected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if(mTempBitmap==null || (mTempBitmap.getWidth()!=canvas.getWidth()
            ||mTempBitmap.getHeight()!=canvas.getHeight()) )
    {
        mTempBitmap = Bitmap.createBitmap(canvas.getWidth(), 
                canvas.getHeight(), Bitmap.Config.ARGB_8888);
        mTempCanvas = new Canvas(mTempBitmap);
    }

    mTempBitmap.eraseColor(0);
    synchronized (mSvgLock) {
        mTempCanvas.save();
        mTempCanvas.translate(getPaddingLeft(), getPaddingTop());
        fill(mTempCanvas);
        final int count = paths.size();
        for (int i = 0; i < count; i++) {
            final SvgUtils.SvgPath svgPath = paths.get(i);
            final Path path = svgPath.path;
            final Paint paint1 = naturalColors ? svgPath.paint : paint;
            mTempCanvas.drawPath(path, paint1);
        }

        fillAfter(mTempCanvas);

        mTempCanvas.restore();

        applySolidColor(mTempBitmap);

        canvas.drawBitmap(mTempBitmap,0,0,null);
    }
}

 
 
If fillColor had value before then we replace untransparent pixels of bitmap by solid color. 
 
private void applySolidColor(final Bitmap bitmap) {
    if(fill && fillColor!= Color.argb(0,0,0,0) )
        if (bitmap != null) {
            for(int x=0;x<bitmap.getWidth();x++)
            {
                for(int y=0;y<bitmap.getHeight();y++)
                {
                    int argb = bitmap.getPixel(x,y);
                    int alpha = Color.alpha(argb);
                    if(alpha!=0)
                    {
                        int red = Color.red(fillColor);
                        int green = Color.green(fillColor);
                        int blue =  Color.blue(fillColor);
                        argb = Color.argb(alpha,red,green,blue);
                        bitmap.setPixel(x,y,argb);
                    }
                }
            }
        }
}
 
Then sets the duration of the animation. Since the AnimatorSet sets the duration for 
each Animator, we have to divide it by the number of paths.


 
public AnimatorSetBuilder(final SVGPathView pathView) {
    paths = pathView.paths;
    for (SvgUtils.SvgPath path : paths) {
        path.setAnimationStepListener(pathView);
        ObjectAnimator animation = ObjectAnimator.ofFloat(path, "length", 0.0f, path.getLength());
        animators.add(animation);
    }
    animatorSet.playSequentially(animators);
} 
 


 

How to use?

Add SVGPathView class in xml layout.
 
<com.truedreamz.svglinedrawing.LineDrawing.SVGPathView
    android:id="@+id/pathView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="30dp"
    android:paddingRight="30dp"
    android:layout_marginTop="80dp"
    android:paddingBottom="30dp"
    app:svg="@raw/ironman"
    app:pathColor="@android:color/holo_blue_dark"
    app:naturalColors="true"
    app:pathWidth="2dp"/>
  
Use the animator for parallel animation
 
 pathView.getPathAnimator().
        delay(100).
        duration(8000).
        interpolator(new AccelerateDecelerateInterpolator()).
        listenerEnd(new SVGPathView.AnimatorBuilder.ListenerEnd() {
            @Override            public void onAnimationEnd() {
                txtViewLoading.setText("Line drawing completed.");
                Toast.makeText(SVGDrawingActivity.this,"Line drawing is done.",Toast.LENGTH_LONG).show();
            }
        }).start();
 
Use the animator for sequential animation

pathView.getSequentialPathAnimator().
        delay(100).
        duration(8000).
        interpolator(new AccelerateDecelerateInterpolator()).
        listenerEnd(new SVGPathView.AnimatorBuilder.ListenerEnd() {
            @Override            public void onAnimationEnd() {
                txtViewLoading.setText("Line drawing completed.");
                Toast.makeText(SVGDrawingActivity.this,"Line drawing is done.",Toast.LENGTH_LONG).show();
            }
        }).start(); 

I have uploaded complete source code in Github, 
https://github.com/JayaprakashR-Zealot/SVGLineDrawing 
 
Kindly raise your queries in the command section. 
Happy coding !!!
Thanks. 

Friday, 2 December 2016

Smooth Parallel (i.e. Horizontal) RecyclerView

            If we need to portray complex list with bulk content with slinky animation ( i.e. material design styles) in your apps, the better choice must be the RecyclerView and CardView widgets.

 In this post, we are going to understand the how to populate images in Recycler view through horizontal direction with customized CardView layout. We also learn custom ItemAnimator, onItemClickListener for RecyclerView as well. This application shows a horizontal category RecyclerView that shows the images of shirt, pant, overcoat, hat, and etc. The choices populate based on selected category from Recycler View. You can find the workflow from below video.




If you want to use a RecyclerView, you will need to work with the following:
  • RecyclerView.Adapter - To handle the data collection and bind it to the view
  • LayoutManager - Helps in positioning the items
  • ItemAnimator - Helps with animating the items for common operations such as Addition or Removal of an item. 

        Moreover, the add or remove data in Listview had been extremely difficult to do. RecyclerView is mandatory to implement the ViewHolder Class that was already a recommended practice in Listview but now deeply integrated with this RecyclerView framework. You can find the detailed explanation for RecyclerView components here.

Why RecyclerView than ListView

  • Required ViewHolder in Adapters - ListView adapters do not require the use of the ViewHolder. In contrast, implementing an adapter for RecyclerView requires the use of the ViewHolder pattern for better performance.    
  • Customizable Item Layouts - The RecyclerView has a RecyclerView.LayoutManager that allows any item layouts including horizontal lists or staggered grids.
  • Easy Item Animations - ListView contains no special provisions through which one can animate the addition or deletion of items. In contrast, the RecyclerView has the RecyclerView.ItemAnimator class for handling item animations.
  • Manual Data Source - ListView had adapters for different sources such as ArrayAdapter and CursorAdapter for arrays and database results respectively. In contrast, the RecyclerView.Adapter requires a custom implementation to supply the data to the adapter.
  • Manual Item Decoration - ListView has the android:divider property for easy dividers between items in the list. In contrast, RecyclerView requires the use of a RecyclerView.ItemDecoration object to setup much more manual divider decorations.
  • Manual Click Detection - ListView has a AdapterView.OnItemClickListener interface for binding to the click events for individual items in the list. In contrast, RecyclerView only has support for RecyclerView.OnItemTouchListener which manages individual touch events but has no built-in click handling.

 Components of a RecyclerView

LayoutManager

        A layout manager positions item views inside a RecyclerView and determines when to reuse item views that are no longer visible to the user. To reuse (or recycle) a view, a layout manager may ask the adapter to replace the contents of the view with a different element from the data set. Recycling views in this manner improve performance by avoiding the creation of unnecessary views or performing expensive layout id lookups.

  • LinearLayoutManager - shows items in a vertical or horizontal scrolling list.
  • GridLayoutManager - shows items in a grid.
  • StaggeredGridLayoutManager - shows items in a staggered grid.

RecyclerView.Adapter

         RecyclerView is a similar approach to the ones you already used, but with some peculiarities, such as a required ViewHolder. You will have to override two main methods:
  • onCreateViewHolder() - One to inflate the view and its view holder.
  • onBindViewHolder() - One to bind data to the view. 
The good thing about this is that first method is called only when we really need to create a new view. No need to check if it’s being recycled.

RecyclerView.ItemAnimator

         The add/delete/select operations are notified to the adapter. DefaultItemAnimator can be used for basic default animations and works quite well.

RecyclerView.OnItemClickListener

By implementing the RecyclerView.OnItemTouchListener to respond to only single tap events. This is just a beginning and extended to provide more complex gestures like long click etc.

Workflow for Parallel Recycler View

Now back to our Parallel Recycler View App.

Step 1 : Add recycler view dependency

compile 'com.android.support:cardview-v7:23.1.0'
compile 'com.android.support:recyclerview-v7:23.1.0'

Add these two dependencies in app.gradle

Step 2 : Add recycler view to activity_main.xml layout

<android.support.v7.widget.RecyclerView
    android:id="@+id/category_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:layout_below="@+id/imageUser"
    android:scrollbars="none" />

Step 3 : Creating model class

public class Category implements Serializable {

    public String cat_name,cat_description;
    public int cat_id,cat_icon;
}
Serializing a class file provides a fast and efficeint way to store information produced by your application. 

Step 4: Creating Adapter class

      The adapter's role is to convert an object to a position into a list row item to be inserted. However,  with a RecyclerView the adapter requires the existence of a "ViewHolder" object which describes and provides access to all the views within each item row.

public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.AKCViewHolder>{

    private Context mContext;
    private List<Category> mCategoryList;
    private final static int FADE_DURATION = 300; // in milliseconds    private int lastPosition = -1;

    public class AKCViewHolder extends RecyclerView.ViewHolder {
        public ImageView categoryIcon;

        public AKCViewHolder(View view) {
            super(view);
            categoryIcon = (ImageView) view.findViewById(R.id.categoryImage);
        }
    }

    public CategoryAdapter(Context mContext, List<Category> cateList) {
        this.mContext = mContext;
        this.mCategoryList = cateList;
    }

    @Override    public AKCViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View categoryItem= LayoutInflater.from(parent.getContext()).inflate
                (R.layout.list_single_card,parent,false);
        return new AKCViewHolder(categoryItem);
    }

    @Override    public void onBindViewHolder(AKCViewHolder holder, int position) {
        Category categoryItem=mCategoryList.get(position);
        Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(),categoryItem.cat_icon);
        holder.categoryIcon.setImageBitmap(icon);

        // Set the view to scale        //setScaleAnimation(holder.categoryIcon);
        if(CATEGORY_SELECTED_POSITION == position){
            // Here I am just highlighting the background            holder.categoryIcon.setBackgroundColor(mContext.getResources()
                    .getColor(R.color.colorPickedItem));
        }else{
            holder.categoryIcon.setBackgroundColor(mContext.getResources()
                    .getColor(R.color.colorItembg));
        }
    }

    @Override    public int getItemCount() {
        return mCategoryList.size();
    }
} 

Every adapter has three primary methods: onCreateViewHolder to inflate the item layout and create the holder, onBindViewHolder to set the view attributes based on the data and getItemCount to determine the number of items.
With the adapter completed, all that is remaining is to bind the data from the adapter into the RecyclerView.

Step 5 : Adding adapter to RecyclerView

mCategoryList=new ArrayList<>();
mCategoryAdapter=new CategoryAdapter(MainActivity.this,mCategoryList);

mCategoryRecyclerView = (RecyclerView) findViewById(R.id.category_recycler_view);
mCategoryRecyclerView.setAdapter(mCategoryAdapter);
In MainActivity.java binding adapter to RecyclerView.

Step 6: Assigning LayoutManager to RecyclerView

// Enable optimizations if the items are static and will not change for significantly smoother scrollingmCategoryRecyclerView.setHasFixedSize(true);

RecyclerView.LayoutManager mCateLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mCategoryRecyclerView.setLayoutManager(mCateLayoutManager);

We assign the HORIZONTAL orientation at LinearLayoutManager for RecyclerView. We have to setHasFixedSize(true) by enabling optimizations if the items are static and will not change for significantly smoother scrolling.

Step 7 : Loading data to Category

private void prepareCategory(){
    int[] mAccessoryIcon = new int[]{
            R.drawable.category_shirt,
            R.drawable.category_pant,
            R.drawable.category_overcoat,
            R.drawable.category_gown,
            R.drawable.category_hat,
            R.drawable.category_tie,
            R.drawable.category_bride,
            R.drawable.category_handbag,
            R.drawable.category_tees,
            R.drawable.category_shoe,
            R.drawable.category_cutshoe,
            };
    String[] mAccessoryName=getResources().getStringArray(R.array.accessoryName);

    for (int i=0;i<mAccessoryIcon.length;i++){
        Category category=new Category();
        category.cat_id=i;
        category.cat_name=mAccessoryName[i];
        category.cat_icon=mAccessoryIcon[i];
        category.cat_description=mAccessoryName[i]+" details";
        mCategoryList.add(category);
    }
    mCategoryAdapter.notifyDataSetChanged();
}

Call prepareCategory() in OnCreate() to load data in mCategoryList ArrayList. Finally, compile and run the app and you should see something like the screenshot below. 

Step 7: Creating custom OnItemClickListener

         An important point that the guide leaves out is about handling clicks on the recycler view items. There is no such thing as OnItemClickListener for RecyclerViews. Given that RecyclerView takes ListView a step further and doesn't have a concept of a row/column, but rather an arbitrarily laid out amount of children, they have delegated the onClick to each one of them, or to programmer implementation. So We should implement OnItemTouchListener to achieve this issue.

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener  {

    private OnItemClickListener mListener;

    public interface OnItemClickListener {
        public void onItemClick(View view, int position);
    }

    GestureDetector mGestureDetector;

    public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
        mListener = listener;
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }
        });
    }

    @Override    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
        View childView = view.findChildViewUnder(e.getX(), e.getY());
        if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
            mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
        }
        return false;
    }

    @Override    public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
    }

    @Override    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
} 

Step 8: Implementing addOnItemTouchListener

mCategoryRecyclerView.addOnItemTouchListener(
        new RecyclerItemClickListener(MainActivity.this, new RecyclerItemClickListener.
          OnItemClickListener() {
            @Override            public void onItemClick(View view, int position) {

                // Updating old as well as new positions 
               mCategoryAdapter.notifyItemChanged(CATEGORY_SELECTED_POSITION);
                CATEGORY_SELECTED_POSITION = position;
                mCategoryAdapter.notifyItemChanged(CATEGORY_SELECTED_POSITION);

                Category category=mCategoryList.get(position);
                Toast.makeText(view.getContext(),"Category : "+
            category.cat_name,Toast.LENGTH_SHORT).show();
                // Load date in choice recyclerview                 
                pickChoice(position);
            }
        }));


By adding addOnItemTouchListener(), we can implement onItemClick in RecyclerView.


I have uploaded the full source code in GitHub,

Happy coding,
Cheers!!!