Horsepower in Android or once again on RecyclerView.LayoutManager

3r3133. Horsepower in Android or once again on RecyclerView.LayoutManager 3r33737.
 3r33724. According to the author, the article can be useful in the same way as it is for novice Android developers making their first steps in such an exciting area. The history of the subject of this note began with the idea to equip the educational project with the so-called “wow effect”. How much it was possible to judge you. I ask all curious under kat.
 3r33724. 3r311.
 3r33724. A demo project with all this ugliness can be found on GitHub at 3r3-3695. link
.
 3r33724.
 3r33724. At the core of the screen that interests us is the well-loved RecyclerView. And the highlight is the fact that when scrolling through the list, one fully visible top element scaled in a special way. This feature is characterized by the fact that the scaling occurs in different ways for the components that make up the list item.
 3r33724.
 3r33724. However, it is better to see once.
 3r33724.
 3r33724. 3r? 3551. 3r33552. Fig. 1. General view of 3r35353. 3r35454. 3r3133. 3r3334. 3r33737.
 3r33724. 3r33737. 3r33737.
 3r33724. Consider the list item in detail. In the project, it is implemented as a LaunchItemView class inherited from CardView. Its markup contains the following components:
 3r33724.
 3r33724. 3r33535.  3r33724. 3r337. Image - the ScaledImageView class, designed to render an image with a given height (scaling). 3r33737.  3r33724. 3r337. Headline 3r33737.  3r33724. 3r337. Explanatory text. 3r33737.  3r33724. 3r33540.
 3r33724. 3r3133. 3r361 3r33737.
 3r33724. [i] Fig. 2. The structure of the list item (LaunchItemView). 3r33140.
 3r33724.
 3r33724. In the process of scrolling the list, the following happens: 3r3703.  3r33724.
 3r33724.
 3r33724. 3r337. The element height changes from the minimum value (equal to the height of the heading with decoration) to the maximum value (equal to the height that allows displaying the title and explanatory text, with decoration). 3r33737.  3r33724. 3r337. The height of the image is equal to the height of the element minus decoration, the width varies proportionally. 3r33737.  3r33724. 3r337. The relative position inside the element and the size of the explanatory text remains unchanged. 3r33737.  3r33724. 3r337. The magnitude of the scaling is limited to the top with a minimum size sufficient to display all the content, taking into account the decoration and below with a minimum size sufficient to display the title, taking into account the decoration. 3r33737.  3r33724. 3r337. Scaling, other than boundary values, is applied to the top fully visible list item. The elements above it have a maximum scale, below - the minimum. 3r33737.  3r33724. 3r33737.
 3r33724. Thus, when scrolling up, the effect of gradual “extrusion” of the contents of the element with proportional scaling of the image is created. When scrolling down, the opposite effect is observed.
 3r33724.
 3r33724. I solved the problem posed in this way by creating a LayoutManager for RecyclerView and two additional components. But first things first.
 3r33724.
 3r33724.

LaunchLayoutManager


 3r33724. My training project is devoted to space topics, so the components have received the appropriate names.
 3r33724.
 3r33724. Studying the topic of creating an arbitrary LayoutManager, I found two good articles on this topic,[1, 2]. I will not repeat their contents. Instead, I’ll focus on the most interesting points of my decision.
 3r33724.
 3r33724. Performing the decomposition of the task, I singled out two main stages: 3r3703.  3r33724.
 3r33724.
 3r33724. 3r337. The definition of the index of the first and last element, which are fully or partially visible on the screen. 3r33737.  3r33724. 3r337. Rendering of visible elements with the necessary scaling. 3r33737.  3r33724. 3r33737.
 3r33724. In general, the location and size of the elements of the list looks like this:
 3r33724.
 3r33724. 3r3133. 3r33737.
 3r33724. [i] Figure 3. RecyclerView and its elements. 3r33140.
 3r33724.
 3r33724.

Definition of a closed interval of indices of visible elements


 3r33724. In fig. 3 visible elements include indexes from 3 to 11 inclusive. Moreover, according to our algorithm, the elements with indices 0-3 have a maximum size, the elements 5-12 have a minimum size, and an element with an index of 4 is intermediate between the minimum and maximum.
 3r33724.
 3r33724. As you might guess, one of the key points in determining the minimum and maximum index of visible elements is the offset by which the list is swept relative to the upper boundary of the visible area.
 3r33724.
 3r33724. Consider the method calculateVisiblePositions, designed to determine these values.
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private void calculateVisiblePositions () {
2 if (mBigViewHeight! = 0) {
3 mMaximumOffset = (getItemCount () - 1) * mBigViewHeight; 3r33724. 4 mFirstVisibleViewPosition = mOffset /mBigViewHeight; 3r33724. 5 if (mFirstVisibleViewPosition> getItemCount () - 1) {
6 mFirstVisibleViewPosition = 0; 3r33724. 7 mOffset = 0; 3r33724. 8} 3r32424. 9
10 mLastVisibleViewPosition = mFirstVisibleViewPosition; 3r33724. 11 int emptyHeight = getHeight (); 3r33724. 12 mFirstVisibleViewTopValue =
mBigViewHeight * mFirstVisibleViewPosition - mOffset; 3r33724. 13 int firstVisibleViewBottomValue =
mFirstVisibleViewTopValue + mBigViewHeight; 3r33724. 14 emptyHeight - = firstVisibleViewBottomValue; 3r33724. 15 int secondVisibleViewHeight =
getViewHeightByTopValue (firstVisibleViewBottomValue); 3r33724. 16 if (emptyHeight - secondVisibleViewHeight> = 0) {
17 emptyHeight - = secondVisibleViewHeight; 3r33724. 18 mLastVisibleViewPosition ++; 3r33724. 19 int smallViewPosCount = emptyHeight /mSmallViewHeight; 3r33724. 20 mLastVisibleViewPosition + = smallViewPosCount; 3r33724. 21 emptyHeight - = smallViewPosCount * mSmallViewHeight; 3r33724. 22 if (emptyHeight> 0) {3r33724. 23 mLastVisibleViewPosition ++; 3r33724. 24} 3r3244. 25} 3r3724. 26 if (mLastVisibleViewPosition> getItemCount () - 1) {
27 mLastVisibleViewPosition = getItemCount () - 1; 3r33724. 28} 3r3244. 29 Timber.d ("calculateVisiblePositions
MFirstVisibleViewPosition:% d,
MLastVisibleViewPosition:% d",
MFirstVisibleViewPosition,
MLastVisibleViewPosition); 3r33724. 30} 3r32424. 31} 3r3244. 3r3675.
 3r33724. Line 2 - check if the height of the element of maximum size is determined, which displays all the content (title, explanatory text and image). If not, then it makes no sense to continue.
 3r33724.
 3r33724. Line 3 - we calculate how many places, except for one item scrolled up, will take up the maximum allowable offset. This value will limit the offset value in the scrollVerticallyBy method.
 3r33724.
 3r33724. Line 4 - calculate the index of the first visible element. Since the mFirstVisibleViewPosition variable is of integer type, due to the drop of the fractional part, we automatically take into account the case of the partially visible first element.
 3r33724.
 3r33724. Lines 5-8 - checks if the index of the first visible item exceeds the index of the last item in the list. This can happen, for example, when the list was first scrolled up, and then the number of elements decreased, for example, by applying a filter. In this case, simply “rewind” the list to the beginning.
 3r33724.
 3r33724. Line 10 - use the index of the first visible element as a starting point to search for the index of the last.
 3r33724.
 3r33724. Line 11 - set the height of the visible area. This value will decrease during the search for the maximum index of visible elements.
 3r33724.
 3r33724. Lines 1? 13 - we define the coordinates top and bottom of the rectangle of drawing the first element.
 3r33724.
 3r33724. Line 14 - we reduce the free visible area by the size of the visible part of the first element. Those. as if we are virtually placing the first element on the screen.
 3r33724.
 3r33724. Line 15 - calculate the height of the second visible element. This element is potentially subject to scaling (see paragraph 5 of the algorithm). The getViewHeightByTopValue method is described in detail below.
 3r33724.
 3r33724. Line 16 - check if there is still free space after the “virtual placement” of the second element on the screen.
 3r33724.
 3r33724. Line 17 - we fix how much free space is left.
 3r33724.
 3r33724. Line 18 - increment the index of the last visible item.
 3r33724.
 3r33724. Line 19 - we calculate the largest number of elements of the minimum size that can fit in the remaining free space and at the same time will be fully visible.
 3r33724.
 3r33724. Line 20 - increase the index of the last visible element by the calculated value.
 3r33724.
 3r33724. Lines 21-24 - check if there is room for partial placement of another element. If so, increase the index by one more.
 3r33724.
 3r33724. Now about the method that calculates the height of the second visible element depending on the position on the screen - the top coordinates of the display rectangle of this element.
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private int getViewHeightByTopValue (int topValue) {
2 topValue - = mTopAndBottomMargins; 3r33724. 3 if (topValue> mBigViewHeight) {
4 topValue = mBigViewHeight; 3r33724. 5} else if (topValue < 0) {
6 topValue = 0;
7}
8 float scale = 1 - (float) topValue /mBigViewHeight;
9 int height =
(Int) (mSmallViewHeight +). - mSmallViewHeight));
10 Timber.d ("getViewHeightByTopValue
topValue:% d, scale:% f, height:% d",
topValue, scale, height);
11 height;
737 d.}
3r336755.
 3r33724. Line 2 - drop the lower and upper margin.
 3r33724.
 3r33724. Lines 3-7 - for correct calculation of the scale, we limit the top from the top to the maximum height of the element, and from below to zero.
 3r33724.
 3r33724. Line 8 - we calculate the scaling factor, which takes the value 1 for the maximally expanded element and 0 for the minimum. For the correctness of this particular result, we need restrictions in lines 3-7.
 3r33724.
 3r33724. Line 9 - we calculate the height of the element as an increase to the minimum height and the difference between the maximum and minimum height, taking into account the scaling factor. Those. when the coefficient is ? the height is minimal, and when 1 - minimum + (maximum - minimum) = maximum.
 3r33724.
 3r33724. So now we know the first and last indexes of the elements to be drawn. It's time to do it!
 3r33724.
 3r33724.

Drawing elements with the necessary scaling 3r3702.
 3r33724. Since the process of drawing is cyclical, then immediately before drawing we will warm up the cache with already existing RecyclerView elements (if there are any, of course). This technique is covered in[1, 2]and here I will not dwell on it.
 3r33724.
 3r33724. Consider the fillDown method, designed to draw elements moving from top to bottom along the available visible area.
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private void fillDown (RecyclerView.Recycler recycler) {
2 boolean isViewFromCache; 3r33724. 3 int topValue = mFirstVisibleViewTopValue; 3r33724. 4 int bottomValue; 3r33724. 5 int viewHeight; 3r33724. 6 try {
7 for (int curPos = mFirstVisibleViewPosition;
CurPos <= mLastVisibleViewPosition; curPos++) {
8 isViewFromCache = true;
9 View view = mViewCache.get (curPos); 3rrr2424. 10 if (view == null) {3r3373724. .12 view = recycler.getViewForPosition (curPos);
13} else {
14 mViewCache.remove (curPos);
15}
16 viewHeight = getViewHeightByTopValue (topValue); 3rrrrrrrrrrrrrrrrrr = 3rrrr = 3rrr =
r.
18 if (view instanceof LaunchItemView) {
19 ((LaunchItemView) view) .updateContentSize (viewHeight);
20}
21 if (isViewFromCache) {
22 if (view.rtT)) ) {
23 view.setTop (topValue);
24}
25 if (view.getBottom ()! = BottomValue - mTopAndBottomMargins) {
26 view.setBottom (bottomValue - mTopAndBottomMargins); 3r33724. 27} 3r3244. 28 attachView (view); 3r33724. 29} else {
30 layoutView (view, topValue, bottomValue); 3r33724. 31} 3r3244. 32 topValue = bottomValue; 3r33724. 33} 3r32424. 34} catch (Throwable throwable) {
35 Timber.d (throwable); 3r33724. 36} 3r32424. 37} 3r3244. 3r3675.
 3r33724. Line 3 - we initiate the variable topValue by the coordinate of the top of the first visible element. From the stove of this value and will continue to dance.
 3r33724.
 3r33724. Line 7 - we initiate a loop on the indices of the elements to be drawn.
 3r33724.
 3r33724. Line 8 - optimistically we assume that we will find the item we need in the cache.
 3r33724.
 3r33724. Line 9 - look at the cache (with hope).
 3r33724.
 3r33724. Line 10-12 - if the item we need is not in the cache, we request it from an instance of the RecyclerView.Recycler class, which returns a view initialized with data from the adapter for a specific position.
 3r33724.
 3r33724. Line 14 - if the item was still in the cache, delete it from there.
 3r33724.
 3r33724. Line 16 - we calculate the height of the element depending on its position on the screen.
 3r33724.
 3r33724. Line 17 - calculate the lower boundary of the element.
 3r33724.
 3r33724. Lines 18-20 - we scale the content of the element, if it knows how to do it.
 3r33724.
 3r33724. Line 21 - it is important for us to understand whether the current view was previously drawn (taken from the cache) or whether we received a new instance. These two options require different approaches.
 3r33724.
 3r33724. Lines 22-28 - if the view is obtained from the cache, then, if necessary, we change the values ​​of the coordinates top and bottom, and append the view.
 3r33724.
 3r33724. Line 30 - if the view is not from the cache, then to display the element we use the layoutView method, which is described below.
 3r33724.
 3r33724. Line 32 - we shift the topValue to the lower border of the view just drawn so that this value becomes the starting point for the next iteration of the loop.
 3r33724.
 3r33724. Now about the layoutView method for displaying a new list item obtained from an instance of the RecyclerView.Recycler class.
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private void layoutView (View view, int top, int bottom) {
2 addView (view); 3r33724. 3 measureChildWithMargins (view, ? 0); 3r33724. 4 int decoratedMeasuredWidth = getDecoratedMeasuredWidth (view); 3r33724. 5 RecyclerView.LayoutParams layoutParams =
(RecyclerView.LayoutParams) view.getLayoutParams (); 3r33724. 6
7 layoutDecorated (view,
8 layoutParams.leftMargin,
9 top + layoutParams.topMargin,
10 decoratedMeasuredWidth + layoutParams.rightMargin,
11 bottom + layoutParams.bottomMargin); 3r33724. 12} 3r32424. 3r3675.
 3r33724. Line 2 - add view to RecyclerView.
 3r33724.
 3r33724. Line 3 - measure the view.
 3r33724.
 3r33724. Line 4 - define the width of the view.
 3r33724.
 3r33724. Line 5 - we get the layout view parameters.
 3r33724.
 3r33724. Line 7 - we actually draw the view in the received coordinates.
 3r33724.
 3r33724.
Scaling content


 3r33724. Of the entire structure of the list item, only the image is subject to scaling. The logic of this scaling is encapsulated in the ScaledImageView class inherited from View.
 3r33724.
 3r33724. In our case, the execution of scaling is required at arbitrary points in time, and depends on external factors that we cannot control, for example, on how intensively the user scrolls the list. Since this very organically falls into the paradigm of reactive programming, I could not miss the chance to practice with RxJava and the hot data source.
 3r33724.
 3r33724. We will use PublishProcessor to create a stream of integer values ​​that determine the desired image height:
 3r33724.
 3r33724. 3r3661. 3r3662. private PublishProcessor
mScalePublishProcessor; 3r3675.
 3r33724. Accordingly, to perform scaling, we simply generate another stream element with the required value:
 3r33724.
 3r33724. 3r3661. 3r3662. public void setImageHeight (int height) {
mScalePublishProcessor.onNext (height); 3r33724.}
3r3675.
 3r33724. This is how asynchronous processing of this stream takes place:
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private void initObserver () {
2 mScalePublishProcessor
3 .filter (value -> value> 0
&& value! = MBitmapHeight
&& mOriginalBitmap! = Null)
4 .onBackpressureBuffer (? 3r32424. 5 () -> Timber.d ("initObserver: buffer overflow"),
BackpressureOverflowStrategy.DROP_OLDEST)
6 .observeOn (Schedulers.computation (), false, 1)
7 .map (this :: createScaledBitmap)
8 .map (this :: setScaledBitmap)
9 .subscribe (
10 (value) -> {
11 invalidate ();
12 Timber.d ("initObserver invalidate ThreadId:% d", 3rr3724. Thread.currentThread (). GetId ());
.13}, Timber :: d); 3r33724. 14} 3r3244. 3r3675.
 3r33724. Line 3 - perform the initial filtering:
 3r33724.
 3r33724. 3r33535.  3r33724. 3r337. Drop values ​​less than or equal to zero. 3r33737.  3r33724. 3r337. We do not consider values ​​whose value is equal to the height of the image scaled earlier. Those. the case when scaling is no longer required. 3r33737.  3r33724. 3r337. Do not perform scaling when the original image is not initialized. Initialization is discussed below in the setBitmap method. 3r33737.  3r33724. 3r33540.
 3r33724. Line 4 - use back pressure with a buffer size of 1 element and a strategy of displacing the older element from the buffer. Due to this, we will always receive the most current value of the height for scaling. In our case, this is very important, because we have a hot source that, in response to user actions (for example, intensive scrolling of the list), will generate elements faster than we can process them (perform scaling). In such conditions, it makes no sense to accumulate values ​​in the buffer and process these elements sequentially, since they are already outdated, the user has already “squandered” this state.
 3r33724.
 3r33724. To illustrate and enhance the effect, I added a delay of 25 ms to the image scaling method (createScaledBitmap) and below resulted in two visualizations: without using back pressure (left) and with back pressure (right). The interface on the left is clearly lagging behind the actions of the user, living some kind of life. Right - lost in smoothness due to the additional delay in the scaling method, but not in responsiveness.
 3r33724.
 3r33724. 3r? 3551. 3r33552. Comparison 3r35353. 3r35454. 3r33555.  3r33724. 3r33557.  3r33724. 3r33535. Without back pressure 3r33565.  3r33724. 3r33535. With back pressure 3r33535. 3r33565.  3r33724. 3r3-3567.  3r33724. 3r? 3569.
 3r33724. 3r33737. 3r33737.
 3r33724. Line 6 - we transfer work to the Schedulers.computation () stream with indication of the buffer size.
 3r33724.
 3r33724. Line 7 - we perform scaling (see the description of the method below).
 3r33724.
 3r33724. Line 8 - set the scaled image to display.
 3r33724.
 3r33724. Line 9 - subscribe to the stream.
 3r33724.
 3r33724. Line 11 - at the end of scaling we redraw the element.
 3r33724.
 3r33724. The createScaledBitmap method directly involved in obtaining an image of the desired size:
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private Bitmap createScaledBitmap (Integer value) {
2 Timber.d ("createScaledBitmap value:% d", value); 3r33724. 3 if (value> mHeightSpecSize) {
4 value = mHeightSpecSize; 3r33724. 5} 3r32424. 6 return Bitmap.createScaledBitmap (mOriginalBitmap, value, value, false); 3r33724. 7} 3r32424. 3r3675.
 3r33724. Lines 3-5 - we will limit the maximum height to the size of view, which is calculated in the onMeasure method.
 3r33724.
 3r33724. Line 6 - create an image of the desired size from the original.
 3r33724.
 3r33724. In the setScaledBitmap method, we save the scaled image for displaying in view:
 3r33724.
 3r33724. 3r3661. 3r3662. 1 private Boolean setScaledBitmap (Bitmap bitmap) {
2 try {3r33724. 3 mBitmapLock.lock (); 3r33724. 4 if (bitmap! = MDrawBitmap && mDrawBitmap! = Null) {
5 mDrawBitmap.recycle (); 3r33724. 6} 3r32424. 7 mDrawBitmap = bitmap; 3r33724. 8 mBitmapHeight = bitmap.getHeight (); 3r33724. 9} catch (Throwable throwable) {
10 Timber.d (throwable); 3r33724. 11} 3r3244. 12 mBitmapLock.unlock (); 3r33724. 13 return true; 3r33724. 14} 3r3244. 3r3675.
 3r33724. Lines ? 12 - we use a lock to synchronize the access to the variable containing the image that is to be drawn on the screen.
 3r33724.
 3r33724. Lines 4-6 - recycle the previously created image.
 3r33724.
 3r33724. Lines 7-8 - remember the new image and its size.
 3r33724.
 3r33724. The setBitmap method sets the original image:
 3r33724.
 3r33724. 3r3661. 3r3662. 1 public void setBitmap (Bitmap bitmap) {
2 if (bitmap! = Null) {
3 mOriginalBitmap = Bitmap.createScaledBitmap (bitmap,
MWidthSpecSize, mHeightSpecSize, false); 3r33724. 4 if (bitmap! = MOriginalBitmap) {
5 bitmap.recycle (); 3r33724. 6} 3r32424. 7 int height = mBitmapHeight; 3r33724. 8 mBitmapHeight = 0; 3r33724. 9 setImageHeight (height); 3r33724. 10} 3r32424. 11} 3r3244. 3r3675.
 3r33724. Line 3 - we scale the original image to the size of the view. This will allow us to save resources when scaling in the createScaledBitmap method if the original image exceeds the view in size.
 3r33724.
 3r33724. Line 4-6 - recycle the old original image.
 3r33724.
 3r33724. Lines 7-9 - reset the height for scaling to overcome the filter in the initObserver method, and produce a stream element to redraw the new image at the desired scale.
 3r33724.
 3r33724.

The result is


 3r33724. As part of the article, I tried to clearly explain some of the ideas that came to me during the work on the educational project. The demo project can be found on GitHub at 3r3-39595. link
. With comments, suggestions, suggestions and criticism I ask in the comments.
 3r33724.
 3r33724.

Related links


 3r33724.
 3r33724. 3r337. Recipes for Android: How to make a delicious LayoutManager 3r33737.  3r33724. 3r337. 3r33737. Building a RecyclerView LayoutManager - Part 1
3r33737.  3r33724. 3r33737. 3r33737. 3r33724. 3r33724. 3r33724.
3r33724. 3r33737.
+ 0 -

Comments 34

Offline
Neon
Neon 22 February 2019 14:19
处理您的编码因为这是我们需要关注的第一件事。https://essaypinglun.wordpress.com/easy-essay-org-评论/ 有很多你需要观看的课程。
Offline
sohail khatri
sohail khatri 1 July 2019 13:18
The website is looking bit flashy and it catches the visitors eyes. Design is pretty simple and a good user friendly interface.used test equipment



The website is looking bit flashy and it catches the visitors eyes. Design is pretty simple and a good user friendly interface.used lab equipment for sale

Offline
sohail khatri
sohail khatri 1 July 2019 14:52
Great post, and great website. Thanks for the information!maxbet bola

Offline
sohail khatri
sohail khatri 2 July 2019 15:11
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..<fast personal loans online bad credit from UnitedFinances.com

Offline
sohail khatri
sohail khatri 4 July 2019 13:17
The website is looking bit flashy and it catches the visitors eyes. Design is pretty simple and a good user friendly interface.click here



The website is looking bit flashy and it catches the visitors eyes. Design is pretty simple and a good user friendly interface.in hindi

Offline
sohail khatri
sohail khatri 4 July 2019 13:48
Excellent article. Very interesting to read. I really love to read such a nice article. Thanks! keep rocking.In hindi and english

Offline
sohail khatri
sohail khatri 5 July 2019 21:12
I recently found many useful information in your website especially this blog page. Among the lots of comments on your articles. Thanks for sharing.買大學學位

Offline
sohail khatri
sohail khatri 7 July 2019 14:15
I read that Post and got it fine and informative.realitypaper

Offline
sohail khatri
sohail khatri 7 July 2019 14:35
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..pname com facebook orca

Offline
sohail khatri
sohail khatri 8 July 2019 12:51
I think this is an informative post and it is very useful and knowledgeable. therefore, I would like to thank you for the efforts you have made in writing this article.נופש בט"ו באב

Offline
sohail khatri
sohail khatri 8 July 2019 16:38
I haven’t any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us.FULL SPECTRUM CBD OIL FOR SALE



I haven’t any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us.BROAD SPECTRUM CBD OIL



This particular is usually apparently essential and moreover outstanding truth along with for sure fair-minded and moreover admittedly useful My business is looking to find in advance designed for this specific useful stuffs…TANTRIC MASSAGE LONDON

Offline
sohail khatri
sohail khatri 8 July 2019 21:26
Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! Cheers, keep doing awesome!PINEAL GLAND


When your website or blog goes live for the first time, it is exciting. That is until you realize no one but you and your.Domain



Thank you because you have been willing to share information with us. we will always appreciate all you have done here because I know you are very concerned with our.
www.missouritaxliencertificates.com

Offline
sohail khatri
sohail khatri 9 July 2019 13:42
This was a really great contest and hopefully I can attend the next one. It was alot of fun and I really enjoyed myself..Quickbooks customer service phone number

Offline
Rank Feed
Rank Feed 11 July 2019 12:33
Nice to be visiting your blog again, it has been months for me. Well this article that i've been waited for so long. I need this article to complete my assignment in the college, and it has same topic with your article. Thanks, great share.pname com facebook orca
Offline
sohail khatri
sohail khatri 11 July 2019 17:17
It is a great website.. The Design looks very good.. Keep working like that!. fortnite account generator

Offline
sohail khatri
sohail khatri 14 July 2019 11:02
This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free.used testing equipment for sale

Offline
sohail khatri
sohail khatri 14 July 2019 15:17
Interesting topic for a blog. I have been searching the Internet for fun and came upon your website. Fabulous post. Thanks a ton for sharing your knowledge! It is great to see that some people still put in an effort into managing their websites. I'll be sure to check back again real soon.hay day free diamonds

Offline
sohail khatri
sohail khatri 14 July 2019 16:46
I havent any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us.rebel wilson weight loss

Offline
sohail khatri
sohail khatri 15 July 2019 14:24
Wow, cool post. I'd like to write like this too - taking time and real hard work to make a great article... but I put things off too much and never seem to get started. Thanks though.bird aviary for sale



Thanks For sharing this Superb article.I use this Article to show my assignment in college.it is useful For me Great Work.pname com facebook orca
Offline
sohail khatri
sohail khatri 15 July 2019 15:15
nice bLog! its interesting. thank you for sharing....Read More

Offline
website templates
website templates 15 July 2019 16:08
I really thank you for the valuable info on this great subject and look forward to more great posts<a href="https://github.com/designmodo/html-website-templates">website templates</a>



I really thank you for the valuable info on this great subject and look forward to more great posts..  website templates
Offline
sohail khatri
sohail khatri 15 July 2019 17:01
Very useful post. This is my first time i visit here. I found so many interesting stuff in your blog especially its discussion. Really its great article. Keep it up.Google ad partner South Africa

Offline
sohail khatri
sohail khatri 17 July 2019 17:33
I read that Post and got it fine and informative.9anime

Offline
sohail khatri
sohail khatri 17 July 2019 22:20
Most of the time I don’t make comments on websites, but I'd like to say that this article really forced me to do so. Really nice post!accessories

Offline
sohail khatri
sohail khatri 18 July 2019 14:11
Hello, I have browsed most of your posts. This post is probably where I got the most useful information for my research. Thanks for posting, maybe we can see more on this. Are you aware of any other websites on this subject.electronic parts distributors

Offline
sohail khatri
sohail khatri 25 July 2019 11:55
I think this is an informative post and it is very useful and knowledgeable. therefore, I would like to thank you for the efforts you have made in writing this article.<a href="https://voyance-retour-amour.com">voyance gratuite par telephone</a>

Offline
sohail khatri
sohail khatri 27 July 2019 15:45
I would like to say that this blog really convinced me to do it! Thanks, very good post.dating agency uk

Offline
sohail khatri
sohail khatri 3 August 2019 11:50
Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with extra information? It is extremely helpful for me.
brother mfc 490 cw drivers

Offline
sohail khatri
sohail khatri 4 August 2019 11:38
Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with extra information? It is extremely helpful for me.
leesa sapira

Offline
Rank Feed
Rank Feed 7 September 2019 13:09
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..calgary seo

Add comment