移植自定义控件View代码
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2009 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_longAnimTime"
|
||||
android:fromYDelta="100%p"
|
||||
android:toYDelta="0" />
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2009 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_longAnimTime"
|
||||
android:fromYDelta="-100%p"
|
||||
android:toYDelta="0" />
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2009 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_longAnimTime"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="100%p" />
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2009 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_longAnimTime"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="-100%p" />
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 390 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 445 B |
After Width: | Height: | Size: 902 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 429 B |
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:endColor="@color/shadow_end"
|
||||
android:startColor="@color/shadow_start" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="@color/shadow_end"
|
||||
android:startColor="@color/shadow_start" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<gradient
|
||||
android:angle="180"
|
||||
android:endColor="@color/shadow_end"
|
||||
android:startColor="@color/shadow_start" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@color/shadow_end"
|
||||
android:startColor="@color/shadow_start" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<solid android:color="#40000000" />
|
||||
|
||||
<!--
|
||||
I know the android:radius is useless here but it's needed to fix an old bug:
|
||||
http://code.google.com/p/android/issues/detail?id=939
|
||||
-->
|
||||
<corners
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp"
|
||||
android:radius="1dp"
|
||||
android:topLeftRadius="@dimen/indicator_corner_radius"
|
||||
android:topRightRadius="@dimen/indicator_corner_radius" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<solid android:color="#40000000" />
|
||||
|
||||
<!--
|
||||
I know the android:radius is useless here but it's needed to fix an old bug:
|
||||
http://code.google.com/p/android/issues/detail?id=939
|
||||
-->
|
||||
<corners
|
||||
android:bottomLeftRadius="@dimen/indicator_corner_radius"
|
||||
android:bottomRightRadius="@dimen/indicator_corner_radius"
|
||||
android:radius="1dp"
|
||||
android:topLeftRadius="0dp"
|
||||
android:topRightRadius="0dp" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="360" >
|
||||
<shape
|
||||
android:innerRadiusRatio="3"
|
||||
android:shape="ring"
|
||||
android:thicknessRatio="8"
|
||||
android:useLevel="false" >
|
||||
<gradient
|
||||
android:centerColor="#FFFFFF"
|
||||
android:centerY="0.50"
|
||||
android:endColor="#1E90FF"
|
||||
android:startColor="#000000"
|
||||
android:type="sweep"
|
||||
android:useLevel="false" />
|
||||
</shape>
|
||||
</rotate>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_inner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
|
||||
android:paddingLeft="@dimen/header_footer_left_right_padding"
|
||||
android:paddingRight="@dimen/header_footer_left_right_padding"
|
||||
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pull_to_refresh_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pull_to_refresh_progress"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</merge>
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_inner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
|
||||
android:paddingLeft="@dimen/header_footer_left_right_padding"
|
||||
android:paddingRight="@dimen/header_footer_left_right_padding"
|
||||
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|center_vertical" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pull_to_refresh_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pull_to_refresh_progress"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_to_refresh_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_to_refresh_sub_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</merge>
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2011 Johan Nilsson <http://markupartist.com>
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/pull_to_refresh_header"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="15dip"
|
||||
android:paddingTop="10dip" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pull_to_refresh_progress"
|
||||
style="@drawable/view_pull_refresh_progress_small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:layout_marginTop="10dip"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pull_to_refresh_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:gravity="center"
|
||||
android:src="@drawable/view_pull_refresh_pull"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_to_refresh_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingTop="5dip"
|
||||
android:text="@string/pull_to_refresh_tap_label"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_to_refresh_updated_at"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pull_to_refresh_text"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/xlistview_footer_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/xlistview_footer_progressbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/xlistview_footer_hint_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="@string/xlistview_footer_hint_normal" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/xlistview_header_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="60dp" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" android:id="@+id/xlistview_header_text">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/xlistview_header_hint_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/xlistview_header_hint_normal" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/xlistview_header_last_time"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/xlistview_header_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/xlistview_header_arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@id/xlistview_header_text"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="-35dp"
|
||||
android:src="@drawable/view_xlistview_arrow" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/xlistview_header_progressbar"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_alignLeft="@id/xlistview_header_text"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="-40dp"
|
||||
android:visibility="invisible" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="PullToRefresh">
|
||||
|
||||
<!-- A drawable to use as the background of the Refreshable View -->
|
||||
<attr name="ptrRefreshableViewBackground" format="reference|color" />
|
||||
|
||||
<!-- A drawable to use as the background of the Header and Footer Loading Views -->
|
||||
<attr name="ptrHeaderBackground" format="reference|color" />
|
||||
|
||||
<!-- Text Color of the Header and Footer Loading Views -->
|
||||
<attr name="ptrHeaderTextColor" format="reference|color" />
|
||||
|
||||
<!-- Text Color of the Header and Footer Loading Views Sub Header -->
|
||||
<attr name="ptrHeaderSubTextColor" format="reference|color" />
|
||||
|
||||
<!-- Mode of Pull-to-Refresh that should be used -->
|
||||
<attr name="ptrMode">
|
||||
<flag name="disabled" value="0x0" />
|
||||
<flag name="pullFromStart" value="0x1" />
|
||||
<flag name="pullFromEnd" value="0x2" />
|
||||
<flag name="both" value="0x3" />
|
||||
<flag name="manualOnly" value="0x4" />
|
||||
|
||||
<!-- These last two are depreacted -->
|
||||
<flag name="pullDownFromTop" value="0x1" />
|
||||
<flag name="pullUpFromBottom" value="0x2" />
|
||||
</attr>
|
||||
|
||||
<!-- Whether the Indicator overlay(s) should be used -->
|
||||
<attr name="ptrShowIndicator" format="reference|boolean" />
|
||||
|
||||
<!-- Drawable to use as Loading Indicator. Changes both Header and Footer. -->
|
||||
<attr name="ptrDrawable" format="reference" />
|
||||
|
||||
<!-- Drawable to use as Loading Indicator in the Header View. Overrides value set in ptrDrawable. -->
|
||||
<attr name="ptrDrawableStart" format="reference" />
|
||||
|
||||
<!-- Drawable to use as Loading Indicator in the Footer View. Overrides value set in ptrDrawable. -->
|
||||
<attr name="ptrDrawableEnd" format="reference" />
|
||||
|
||||
<!-- Whether Android's built-in Over Scroll should be utilised for Pull-to-Refresh. -->
|
||||
<attr name="ptrOverScroll" format="reference|boolean" />
|
||||
|
||||
<!-- Base text color, typeface, size, and style for Header and Footer Loading Views -->
|
||||
<attr name="ptrHeaderTextAppearance" format="reference" />
|
||||
|
||||
<!-- Base text color, typeface, size, and style for Header and Footer Loading Views Sub Header -->
|
||||
<attr name="ptrSubHeaderTextAppearance" format="reference" />
|
||||
|
||||
<!-- Style of Animation should be used displayed when pulling. -->
|
||||
<attr name="ptrAnimationStyle">
|
||||
<flag name="rotate" value="0x0" />
|
||||
<flag name="flip" value="0x1" />
|
||||
</attr>
|
||||
|
||||
<!-- Whether the user can scroll while the View is Refreshing -->
|
||||
<attr name="ptrScrollingWhileRefreshingEnabled" format="reference|boolean" />
|
||||
|
||||
<!--
|
||||
Whether PullToRefreshListView has it's extras enabled. This allows the user to be
|
||||
able to scroll while refreshing, and behaves better. It acheives this by adding
|
||||
Header and/or Footer Views to the ListView.
|
||||
-->
|
||||
<attr name="ptrListViewExtrasEnabled" format="reference|boolean" />
|
||||
|
||||
<!--
|
||||
Whether the Drawable should be continually rotated as you pull. This only
|
||||
takes effect when using the 'Rotate' Animation Style.
|
||||
-->
|
||||
<attr name="ptrRotateDrawableWhilePulling" format="reference|boolean" />
|
||||
|
||||
<!-- BELOW HERE ARE DEPRECEATED. DO NOT USE. -->
|
||||
<attr name="ptrAdapterViewBackground" format="reference|color" />
|
||||
<attr name="ptrDrawableTop" format="reference" />
|
||||
<attr name="ptrDrawableBottom" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="shadow_start">#80000000</color>
|
||||
<color name="shadow_end">#00000000</color>
|
||||
|
||||
<color name="item_n">#ffffff</color>
|
||||
<color name="item_p">#fafafa</color>
|
||||
<color name="table_header">#d7d7d7</color>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="shadow_size">4dip</dimen>
|
||||
<dimen name="table_width">135dp</dimen>
|
||||
<dimen name="table_height">40dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="indicator_right_padding">10dp</dimen>
|
||||
<dimen name="indicator_corner_radius">12dp</dimen>
|
||||
<dimen name="indicator_internal_padding">4dp</dimen>
|
||||
<dimen name="header_footer_left_right_padding">24dp</dimen>
|
||||
<dimen name="header_footer_top_bottom_padding">12dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<item name="tag_type_view" type="id"/>
|
||||
<item name="tag_row" type="id"/>
|
||||
<item name="tag_column" type="id"/>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<item type="id" name="gridview" />
|
||||
<item type="id" name="webview" />
|
||||
<item type="id" name="scrollview" />
|
||||
<item type="id" name="viewpager" />
|
||||
</resources>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="pull_to_refresh_tap_label">Tap to refresh...</string>
|
||||
<string name="pull_to_refresh_pull_label">下拉刷新…</string>
|
||||
<string name="pull_to_refresh_release_label">放开以刷新…</string>
|
||||
<string name="pull_to_refresh_refreshing_label">正在载入…</string>
|
||||
|
||||
<!-- Just use standard Pull Down String when pulling up. These can be set for languages which require it -->
|
||||
<string name="pull_to_refresh_from_bottom_pull_label">@string/pull_to_refresh_pull_label</string>
|
||||
<string name="pull_to_refresh_from_bottom_release_label">@string/pull_to_refresh_release_label</string>
|
||||
<string name="pull_to_refresh_from_bottom_refreshing_label">@string/pull_to_refresh_refreshing_label</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="xlistview_header_hint_normal">下拉刷新</string>
|
||||
<string name="xlistview_header_hint_ready">松开刷新数据</string>
|
||||
<string name="xlistview_header_hint_loading">正在加载...</string>
|
||||
<string name="xlistview_header_last_time">上次更新时间:</string>
|
||||
<string name="xlistview_footer_hint_normal">查看更多</string>
|
||||
<string name="xlistview_footer_hint_ready">松开载入更多</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,59 @@
|
|||
package com.zftlive.android.view.fixedheadtable;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* The Recycler facilitates reuse of views across layouts.
|
||||
*
|
||||
* @author Brais Gab<EFBFBD>n (InQBarna)
|
||||
*/
|
||||
public class Recycler {
|
||||
|
||||
private Stack<View>[] views;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param size
|
||||
* The number of types of view to recycle.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Recycler(int size) {
|
||||
views = new Stack[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
views[i] = new Stack<View>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view to the Recycler. This view may be reused in the function
|
||||
* {@link #getRecycledView(int)}
|
||||
*
|
||||
* @param view
|
||||
* A view to add to the Recycler. It can no longer be used.
|
||||
* @param type
|
||||
* the type of the view.
|
||||
*/
|
||||
public void addRecycledView(View view, int type) {
|
||||
views[type].push(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, if exists, a view of the type <code>typeView</code>.
|
||||
*
|
||||
* @param typeView
|
||||
* the type of view that you want.
|
||||
* @return a view of the type <code>typeView</code>. <code>null</code> if
|
||||
* not found.
|
||||
*/
|
||||
public View getRecycledView(int typeView) {
|
||||
try {
|
||||
return views[typeView].pop();
|
||||
} catch (java.util.EmptyStackException e) {
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,826 @@
|
|||
package com.zftlive.android.view.fixedheadtable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.fixedheadtable.adapter.TableAdapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Scroller;
|
||||
|
||||
/**
|
||||
* This view shows a table which can scroll in both directions. Also still
|
||||
* leaves the headers fixed.
|
||||
*
|
||||
* @author Brais Gab (InQBarna)
|
||||
*/
|
||||
public class TableFixHeaders extends ViewGroup {
|
||||
private int currentX;
|
||||
private int currentY;
|
||||
|
||||
private TableAdapter adapter;
|
||||
private int scrollX;
|
||||
private int scrollY;
|
||||
private int firstRow;
|
||||
private int firstColumn;
|
||||
private int[] widths;
|
||||
private int[] heights;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private View headView;
|
||||
private List<View> rowViewList;
|
||||
private List<View> columnViewList;
|
||||
private List<List<View>> bodyViewTable;
|
||||
|
||||
private int rowCount;
|
||||
private int columnCount;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private Recycler recycler;
|
||||
|
||||
private TableAdapterDataSetObserver tableAdapterDataSetObserver;
|
||||
private boolean needRelayout;
|
||||
|
||||
private final ImageView[] shadows;
|
||||
private final int shadowSize;
|
||||
|
||||
private final int minimumVelocity;
|
||||
private final int maximumVelocity;
|
||||
|
||||
private final Flinger flinger;
|
||||
|
||||
private VelocityTracker velocityTracker;
|
||||
|
||||
private int touchSlop;
|
||||
|
||||
/**
|
||||
* Simple constructor to use when creating a view from code.
|
||||
*
|
||||
* @param context
|
||||
* The Context the view is running in, through which it can
|
||||
* access the current theme, resources, etc.
|
||||
*/
|
||||
public TableFixHeaders(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that is called when inflating a view from XML. This is called
|
||||
* when a view is being constructed from an XML file, supplying attributes
|
||||
* that were specified in the XML file. This version uses a default style of
|
||||
* 0, so the only attribute values applied are those in the Context's Theme
|
||||
* and the given AttributeSet.
|
||||
*
|
||||
* The method onFinishInflate() will be called after all children have been
|
||||
* added.
|
||||
*
|
||||
* @param context
|
||||
* The Context the view is running in, through which it can
|
||||
* access the current theme, resources, etc.
|
||||
* @param attrs
|
||||
* The attributes of the XML tag that is inflating the view.
|
||||
*/
|
||||
public TableFixHeaders(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
this.headView = null;
|
||||
this.rowViewList = new ArrayList<View>();
|
||||
this.columnViewList = new ArrayList<View>();
|
||||
this.bodyViewTable = new ArrayList<List<View>>();
|
||||
|
||||
this.needRelayout = true;
|
||||
|
||||
this.shadows = new ImageView[4];
|
||||
this.shadows[0] = new ImageView(context);
|
||||
this.shadows[0].setImageResource(R.drawable.view_fixheadtable_shadow_left);
|
||||
this.shadows[1] = new ImageView(context);
|
||||
this.shadows[1].setImageResource(R.drawable.view_fixheadtable_shadow_top);
|
||||
this.shadows[2] = new ImageView(context);
|
||||
this.shadows[2].setImageResource(R.drawable.view_fixheadtable_shadow_right);
|
||||
this.shadows[3] = new ImageView(context);
|
||||
this.shadows[3].setImageResource(R.drawable.view_fixheadtable_shadow_bottom);
|
||||
|
||||
this.shadowSize = getResources().getDimensionPixelSize(R.dimen.shadow_size);
|
||||
|
||||
this.flinger = new Flinger(context);
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||
this.touchSlop = configuration.getScaledTouchSlop();
|
||||
this.minimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
this.maximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter currently associated with this widget.
|
||||
*
|
||||
* @return The adapter used to provide this view's content.
|
||||
*/
|
||||
public TableAdapter getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data behind this TableFixHeaders.
|
||||
*
|
||||
* @param adapter
|
||||
* The TableAdapter which is responsible for maintaining the data
|
||||
* backing this list and for producing a view to represent an
|
||||
* item in that data set.
|
||||
*/
|
||||
public void setAdapter(TableAdapter adapter) {
|
||||
if (this.adapter != null) {
|
||||
this.adapter.unregisterDataSetObserver(tableAdapterDataSetObserver);
|
||||
}
|
||||
|
||||
this.adapter = adapter;
|
||||
tableAdapterDataSetObserver = new TableAdapterDataSetObserver();
|
||||
this.adapter.registerDataSetObserver(tableAdapterDataSetObserver);
|
||||
|
||||
this.recycler = new Recycler(adapter.getViewTypeCount());
|
||||
|
||||
scrollX = 0;
|
||||
scrollY = 0;
|
||||
firstColumn = 0;
|
||||
firstRow = 0;
|
||||
|
||||
needRelayout = true;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
boolean intercept = false;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
currentX = (int) event.getRawX();
|
||||
currentY = (int) event.getRawY();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
int x2 = Math.abs(currentX - (int) event.getRawX());
|
||||
int y2 = Math.abs(currentY - (int) event.getRawY());
|
||||
if (x2 > touchSlop || y2 > touchSlop) {
|
||||
intercept = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return intercept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (velocityTracker == null) { // If we do not have velocity tracker
|
||||
velocityTracker = VelocityTracker.obtain(); // then get one
|
||||
}
|
||||
velocityTracker.addMovement(event); // add this movement to it
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
if (!flinger.isFinished()) { // If scrolling, then stop now
|
||||
flinger.forceFinished();
|
||||
}
|
||||
currentX = (int) event.getRawX();
|
||||
currentY = (int) event.getRawY();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int x2 = (int) event.getRawX();
|
||||
final int y2 = (int) event.getRawY();
|
||||
final int diffX = currentX - x2;
|
||||
final int diffY = currentY - y2;
|
||||
currentX = x2;
|
||||
currentY = y2;
|
||||
|
||||
scrollBy(diffX, diffY);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
final VelocityTracker velocityTracker = this.velocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
|
||||
int velocityX = (int) velocityTracker.getXVelocity();
|
||||
int velocityY = (int) velocityTracker.getYVelocity();
|
||||
|
||||
if (Math.abs(velocityX) > minimumVelocity || Math.abs(velocityY) > minimumVelocity) {
|
||||
flinger.start(getActualScrollX(), getActualScrollY(), velocityX, velocityY, getMaxScrollX(), getMaxScrollY());
|
||||
} else {
|
||||
if (this.velocityTracker != null) { // If the velocity less than threshold
|
||||
this.velocityTracker.recycle(); // recycle the tracker
|
||||
this.velocityTracker = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollTo(int x, int y) {
|
||||
if (needRelayout) {
|
||||
scrollX = x;
|
||||
firstColumn = 0;
|
||||
|
||||
scrollY = y;
|
||||
firstRow = 0;
|
||||
} else {
|
||||
scrollBy(x - sumArray(widths, 1, firstColumn) - scrollX, y - sumArray(heights, 1, firstRow) - scrollY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollBy(int x, int y) {
|
||||
scrollX += x;
|
||||
scrollY += y;
|
||||
|
||||
if (needRelayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollBounds();
|
||||
|
||||
/*
|
||||
* TODO Improve the algorithm. Think big diagonal movements. If we are
|
||||
* in the top left corner and scrollBy to the opposite corner. We will
|
||||
* have created the views from the top right corner on the X part and we
|
||||
* will have eliminated to generate the right at the Y.
|
||||
*/
|
||||
if (scrollX == 0) {
|
||||
// no op
|
||||
} else if (scrollX > 0) {
|
||||
while (widths[firstColumn + 1] < scrollX) {
|
||||
if (!rowViewList.isEmpty()) {
|
||||
removeLeft();
|
||||
}
|
||||
scrollX -= widths[firstColumn + 1];
|
||||
firstColumn++;
|
||||
}
|
||||
while (getFilledWidth() < width) {
|
||||
addRight();
|
||||
}
|
||||
} else {
|
||||
while (!rowViewList.isEmpty() && getFilledWidth() - widths[firstColumn + rowViewList.size()] >= width) {
|
||||
removeRight();
|
||||
}
|
||||
if (rowViewList.isEmpty()) {
|
||||
while (scrollX < 0) {
|
||||
firstColumn--;
|
||||
scrollX += widths[firstColumn + 1];
|
||||
}
|
||||
while (getFilledWidth() < width) {
|
||||
addRight();
|
||||
}
|
||||
} else {
|
||||
while (0 > scrollX) {
|
||||
addLeft();
|
||||
firstColumn--;
|
||||
scrollX += widths[firstColumn + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollY == 0) {
|
||||
// no op
|
||||
} else if (scrollY > 0) {
|
||||
while (heights[firstRow + 1] < scrollY) {
|
||||
if (!columnViewList.isEmpty()) {
|
||||
removeTop();
|
||||
}
|
||||
scrollY -= heights[firstRow + 1];
|
||||
firstRow++;
|
||||
}
|
||||
while (getFilledHeight() < height) {
|
||||
addBottom();
|
||||
}
|
||||
} else {
|
||||
while (!columnViewList.isEmpty() && getFilledHeight() - heights[firstRow + columnViewList.size()] >= height) {
|
||||
removeBottom();
|
||||
}
|
||||
if (columnViewList.isEmpty()) {
|
||||
while (scrollY < 0) {
|
||||
firstRow--;
|
||||
scrollY += heights[firstRow + 1];
|
||||
}
|
||||
while (getFilledHeight() < height) {
|
||||
addBottom();
|
||||
}
|
||||
} else {
|
||||
while (0 > scrollY) {
|
||||
addTop();
|
||||
firstRow--;
|
||||
scrollY += heights[firstRow + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositionViews();
|
||||
|
||||
shadowsVisibility();
|
||||
}
|
||||
|
||||
public int getActualScrollX() {
|
||||
return scrollX + sumArray(widths, 1, firstColumn);
|
||||
}
|
||||
|
||||
public int getActualScrollY() {
|
||||
return scrollY + sumArray(heights, 1, firstRow);
|
||||
}
|
||||
|
||||
private int getMaxScrollX() {
|
||||
return Math.max(0, sumArray(widths) - width);
|
||||
}
|
||||
|
||||
private int getMaxScrollY() {
|
||||
return Math.max(0, sumArray(heights) - height);
|
||||
}
|
||||
|
||||
private int getFilledWidth() {
|
||||
return widths[0] + sumArray(widths, firstColumn + 1, rowViewList.size()) - scrollX;
|
||||
}
|
||||
|
||||
private int getFilledHeight() {
|
||||
return heights[0] + sumArray(heights, firstRow + 1, columnViewList.size()) - scrollY;
|
||||
}
|
||||
|
||||
private void addLeft() {
|
||||
addLeftOrRight(firstColumn - 1, 0);
|
||||
}
|
||||
|
||||
private void addTop() {
|
||||
addTopAndBottom(firstRow - 1, 0);
|
||||
}
|
||||
|
||||
private void addRight() {
|
||||
final int size = rowViewList.size();
|
||||
addLeftOrRight(firstColumn + size, size);
|
||||
}
|
||||
|
||||
private void addBottom() {
|
||||
final int size = columnViewList.size();
|
||||
addTopAndBottom(firstRow + size, size);
|
||||
}
|
||||
|
||||
private void addLeftOrRight(int column, int index) {
|
||||
View view = makeView(-1, column, widths[column + 1], heights[0]);
|
||||
rowViewList.add(index, view);
|
||||
|
||||
int i = firstRow;
|
||||
for (List<View> list : bodyViewTable) {
|
||||
view = makeView(i, column, widths[column + 1], heights[i + 1]);
|
||||
list.add(index, view);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private void addTopAndBottom(int row, int index) {
|
||||
View view = makeView(row, -1, widths[0], heights[row + 1]);
|
||||
columnViewList.add(index, view);
|
||||
|
||||
List<View> list = new ArrayList<View>();
|
||||
final int size = rowViewList.size() + firstColumn;
|
||||
for (int i = firstColumn; i < size; i++) {
|
||||
view = makeView(row, i, widths[i + 1], heights[row + 1]);
|
||||
list.add(view);
|
||||
}
|
||||
bodyViewTable.add(index, list);
|
||||
}
|
||||
|
||||
private void removeLeft() {
|
||||
removeLeftOrRight(0);
|
||||
}
|
||||
|
||||
private void removeTop() {
|
||||
removeTopOrBottom(0);
|
||||
}
|
||||
|
||||
private void removeRight() {
|
||||
removeLeftOrRight(rowViewList.size() - 1);
|
||||
}
|
||||
|
||||
private void removeBottom() {
|
||||
removeTopOrBottom(columnViewList.size() - 1);
|
||||
}
|
||||
|
||||
private void removeLeftOrRight(int position) {
|
||||
removeView(rowViewList.remove(position));
|
||||
for (List<View> list : bodyViewTable) {
|
||||
removeView(list.remove(position));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTopOrBottom(int position) {
|
||||
removeView(columnViewList.remove(position));
|
||||
List<View> remove = bodyViewTable.remove(position);
|
||||
for (View view : remove) {
|
||||
removeView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeView(View view) {
|
||||
super.removeView(view);
|
||||
|
||||
final int typeView = (Integer) view.getTag(R.id.tag_type_view);
|
||||
if (typeView != TableAdapter.IGNORE_ITEM_VIEW_TYPE) {
|
||||
recycler.addRecycledView(view, typeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void repositionViews() {
|
||||
int left, top, right, bottom, i;
|
||||
|
||||
left = widths[0] - scrollX;
|
||||
i = firstColumn;
|
||||
for (View view : rowViewList) {
|
||||
right = left + widths[++i];
|
||||
view.layout(left, 0, right, heights[0]);
|
||||
left = right;
|
||||
}
|
||||
|
||||
top = heights[0] - scrollY;
|
||||
i = firstRow;
|
||||
for (View view : columnViewList) {
|
||||
bottom = top + heights[++i];
|
||||
view.layout(0, top, widths[0], bottom);
|
||||
top = bottom;
|
||||
}
|
||||
|
||||
top = heights[0] - scrollY;
|
||||
i = firstRow;
|
||||
for (List<View> list : bodyViewTable) {
|
||||
bottom = top + heights[++i];
|
||||
left = widths[0] - scrollX;
|
||||
int j = firstColumn;
|
||||
for (View view : list) {
|
||||
right = left + widths[++j];
|
||||
view.layout(left, top, right, bottom);
|
||||
left = right;
|
||||
}
|
||||
top = bottom;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
final int w;
|
||||
final int h;
|
||||
|
||||
if (adapter != null) {
|
||||
this.rowCount = adapter.getRowCount();
|
||||
this.columnCount = adapter.getColumnCount();
|
||||
|
||||
widths = new int[columnCount + 1];
|
||||
for (int i = -1; i < columnCount; i++) {
|
||||
widths[i + 1] += adapter.getWidth(i);
|
||||
}
|
||||
heights = new int[rowCount + 1];
|
||||
for (int i = -1; i < rowCount; i++) {
|
||||
heights[i + 1] += adapter.getHeight(i);
|
||||
}
|
||||
|
||||
if (widthMode == MeasureSpec.AT_MOST) {
|
||||
w = Math.min(widthSize, sumArray(widths));
|
||||
} else if (widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
w = sumArray(widths);
|
||||
} else {
|
||||
w = widthSize;
|
||||
int sumArray = sumArray(widths);
|
||||
if (sumArray < widthSize) {
|
||||
final float factor = widthSize / (float) sumArray;
|
||||
for (int i = 1; i < widths.length; i++) {
|
||||
widths[i] = Math.round(widths[i] * factor);
|
||||
}
|
||||
widths[0] = widthSize - sumArray(widths, 1, widths.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (heightMode == MeasureSpec.AT_MOST) {
|
||||
h = Math.min(heightSize, sumArray(heights));
|
||||
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
h = sumArray(heights);
|
||||
} else {
|
||||
h = heightSize;
|
||||
}
|
||||
} else {
|
||||
if (heightMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
w = 0;
|
||||
h = 0;
|
||||
} else {
|
||||
w = widthSize;
|
||||
h = heightSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstRow >= rowCount || getMaxScrollY() - getActualScrollY() < 0) {
|
||||
firstRow = 0;
|
||||
scrollY = Integer.MAX_VALUE;
|
||||
}
|
||||
if (firstColumn >= columnCount || getMaxScrollX() - getActualScrollX() < 0) {
|
||||
firstColumn = 0;
|
||||
scrollX = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
setMeasuredDimension(w, h);
|
||||
}
|
||||
|
||||
private int sumArray(int array[]) {
|
||||
return sumArray(array, 0, array.length);
|
||||
}
|
||||
|
||||
private int sumArray(int array[], int firstIndex, int count) {
|
||||
int sum = 0;
|
||||
count += firstIndex;
|
||||
for (int i = firstIndex; i < count; i++) {
|
||||
sum += array[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation")
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (needRelayout || changed) {
|
||||
needRelayout = false;
|
||||
resetTable();
|
||||
|
||||
if (adapter != null) {
|
||||
width = r - l;
|
||||
height = b - t;
|
||||
|
||||
int left, top, right, bottom;
|
||||
|
||||
right = Math.min(width, sumArray(widths));
|
||||
bottom = Math.min(height, sumArray(heights));
|
||||
addShadow(shadows[0], widths[0], 0, widths[0] + shadowSize, bottom);
|
||||
addShadow(shadows[1], 0, heights[0], right, heights[0] + shadowSize);
|
||||
addShadow(shadows[2], right - shadowSize, 0, right, bottom);
|
||||
addShadow(shadows[3], 0, bottom - shadowSize, right, bottom);
|
||||
|
||||
headView = makeAndSetup(-1, -1, 0, 0, widths[0], heights[0]);
|
||||
|
||||
scrollBounds();
|
||||
adjustFirstCellsAndScroll();
|
||||
|
||||
left = widths[0] - scrollX;
|
||||
for (int i = firstColumn; i < columnCount && left < width; i++) {
|
||||
right = left + widths[i + 1];
|
||||
final View view = makeAndSetup(-1, i, left, 0, right, heights[0]);
|
||||
rowViewList.add(view);
|
||||
left = right;
|
||||
}
|
||||
|
||||
top = heights[0] - scrollY;
|
||||
for (int i = firstRow; i < rowCount && top < height; i++) {
|
||||
bottom = top + heights[i + 1];
|
||||
final View view = makeAndSetup(i, -1, 0, top, widths[0], bottom);
|
||||
columnViewList.add(view);
|
||||
top = bottom;
|
||||
}
|
||||
|
||||
top = heights[0] - scrollY;
|
||||
for (int i = firstRow; i < rowCount && top < height; i++) {
|
||||
bottom = top + heights[i + 1];
|
||||
left = widths[0] - scrollX;
|
||||
List<View> list = new ArrayList<View>();
|
||||
for (int j = firstColumn; j < columnCount && left < width; j++) {
|
||||
right = left + widths[j + 1];
|
||||
final View view = makeAndSetup(i, j, left, top, right, bottom);
|
||||
list.add(view);
|
||||
left = right;
|
||||
}
|
||||
bodyViewTable.add(list);
|
||||
top = bottom;
|
||||
}
|
||||
|
||||
shadowsVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollBounds() {
|
||||
scrollX = scrollBounds(scrollX, firstColumn, widths, width);
|
||||
scrollY = scrollBounds(scrollY, firstRow, heights, height);
|
||||
}
|
||||
|
||||
private int scrollBounds(int desiredScroll, int firstCell, int sizes[], int viewSize) {
|
||||
if (desiredScroll == 0) {
|
||||
// no op
|
||||
} else if (desiredScroll < 0) {
|
||||
desiredScroll = Math.max(desiredScroll, -sumArray(sizes, 1, firstCell));
|
||||
} else {
|
||||
desiredScroll = Math.min(desiredScroll, Math.max(0, sumArray(sizes, firstCell + 1, sizes.length - 1 - firstCell) + sizes[0] - viewSize));
|
||||
}
|
||||
return desiredScroll;
|
||||
}
|
||||
|
||||
private void adjustFirstCellsAndScroll() {
|
||||
int values[];
|
||||
|
||||
values = adjustFirstCellsAndScroll(scrollX, firstColumn, widths);
|
||||
scrollX = values[0];
|
||||
firstColumn = values[1];
|
||||
|
||||
values = adjustFirstCellsAndScroll(scrollY, firstRow, heights);
|
||||
scrollY = values[0];
|
||||
firstRow = values[1];
|
||||
}
|
||||
|
||||
private int[] adjustFirstCellsAndScroll(int scroll, int firstCell, int sizes[]) {
|
||||
if (scroll == 0) {
|
||||
// no op
|
||||
} else if (scroll > 0) {
|
||||
while (sizes[firstCell + 1] < scroll) {
|
||||
firstCell++;
|
||||
scroll -= sizes[firstCell];
|
||||
}
|
||||
} else {
|
||||
while (scroll < 0) {
|
||||
scroll += sizes[firstCell];
|
||||
firstCell--;
|
||||
}
|
||||
}
|
||||
return new int[] { scroll, firstCell };
|
||||
}
|
||||
|
||||
private void shadowsVisibility() {
|
||||
final int actualScrollX = getActualScrollX();
|
||||
final int actualScrollY = getActualScrollY();
|
||||
final int[] remainPixels = {
|
||||
actualScrollX,
|
||||
actualScrollY,
|
||||
getMaxScrollX() - actualScrollX,
|
||||
getMaxScrollY() - actualScrollY,
|
||||
};
|
||||
|
||||
for (int i = 0; i < shadows.length; i++) {
|
||||
setAlpha(shadows[i], Math.min(remainPixels[i] / (float) shadowSize, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setAlpha(ImageView imageView, float alpha) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
imageView.setAlpha(alpha);
|
||||
} else {
|
||||
imageView.setAlpha(Math.round(alpha * 255));
|
||||
}
|
||||
}
|
||||
|
||||
private void addShadow(ImageView imageView, int l, int t, int r, int b) {
|
||||
imageView.layout(l, t, r, b);
|
||||
addView(imageView);
|
||||
}
|
||||
|
||||
private void resetTable() {
|
||||
headView = null;
|
||||
rowViewList.clear();
|
||||
columnViewList.clear();
|
||||
bodyViewTable.clear();
|
||||
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
private View makeAndSetup(int row, int column, int left, int top, int right, int bottom) {
|
||||
final View view = makeView(row, column, right - left, bottom - top);
|
||||
view.layout(left, top, right, bottom);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
||||
final boolean ret;
|
||||
|
||||
final Integer row = (Integer) child.getTag(R.id.tag_row);
|
||||
final Integer column = (Integer) child.getTag(R.id.tag_column);
|
||||
// row == null => Shadow view
|
||||
if (row == null || (row == -1 && column == -1)) {
|
||||
ret = super.drawChild(canvas, child, drawingTime);
|
||||
} else {
|
||||
canvas.save();
|
||||
if (row == -1) {
|
||||
canvas.clipRect(widths[0], 0, canvas.getWidth(), canvas.getHeight());
|
||||
} else if (column == -1) {
|
||||
canvas.clipRect(0, heights[0], canvas.getWidth(), canvas.getHeight());
|
||||
} else {
|
||||
canvas.clipRect(widths[0], heights[0], canvas.getWidth(), canvas.getHeight());
|
||||
}
|
||||
|
||||
ret = super.drawChild(canvas, child, drawingTime);
|
||||
canvas.restore();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private View makeView(int row, int column, int w, int h) {
|
||||
final int itemViewType = adapter.getItemViewType(row, column);
|
||||
final View recycledView;
|
||||
if (itemViewType == TableAdapter.IGNORE_ITEM_VIEW_TYPE) {
|
||||
recycledView = null;
|
||||
} else {
|
||||
recycledView = recycler.getRecycledView(itemViewType);
|
||||
}
|
||||
final View view = adapter.getView(row, column, recycledView, this);
|
||||
view.setTag(R.id.tag_type_view, itemViewType);
|
||||
view.setTag(R.id.tag_row, row);
|
||||
view.setTag(R.id.tag_column, column);
|
||||
view.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY));
|
||||
addTableView(view, row, column);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void addTableView(View view, int row, int column) {
|
||||
if (row == -1 && column == -1) {
|
||||
addView(view, getChildCount() - 4);
|
||||
} else if (row == -1 || column == -1) {
|
||||
addView(view, getChildCount() - 5);
|
||||
} else {
|
||||
addView(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class TableAdapterDataSetObserver extends DataSetObserver {
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
needRelayout = true;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/6219382/842697
|
||||
private class Flinger implements Runnable {
|
||||
private final Scroller scroller;
|
||||
|
||||
private int lastX = 0;
|
||||
private int lastY = 0;
|
||||
|
||||
Flinger(Context context) {
|
||||
scroller = new Scroller(context);
|
||||
}
|
||||
|
||||
void start(int initX, int initY, int initialVelocityX, int initialVelocityY, int maxX, int maxY) {
|
||||
scroller.fling(initX, initY, initialVelocityX, initialVelocityY, 0, maxX, 0, maxY);
|
||||
|
||||
lastX = initX;
|
||||
lastY = initY;
|
||||
post(this);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (scroller.isFinished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean more = scroller.computeScrollOffset();
|
||||
int x = scroller.getCurrX();
|
||||
int y = scroller.getCurrY();
|
||||
int diffX = lastX - x;
|
||||
int diffY = lastY - y;
|
||||
if (diffX != 0 || diffY != 0) {
|
||||
scrollBy(diffX, diffY);
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
if (more) {
|
||||
post(this);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isFinished() {
|
||||
return scroller.isFinished();
|
||||
}
|
||||
|
||||
void forceFinished() {
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.forceFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.zftlive.android.view.fixedheadtable.adapter;
|
||||
|
||||
import android.database.DataSetObservable;
|
||||
import android.database.DataSetObserver;
|
||||
|
||||
/**
|
||||
* Common base class of common implementation for an {@link TableAdapter} that
|
||||
* can be used in {@link TableFixHeaders}.
|
||||
*
|
||||
* @author Brais Gab韓 (InQBarna)
|
||||
*/
|
||||
public abstract class BaseTableAdapter implements TableAdapter {
|
||||
private final DataSetObservable mDataSetObservable = new DataSetObservable();
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
mDataSetObservable.registerObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
mDataSetObservable.unregisterObserver(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the attached observers that the underlying data has been changed
|
||||
* and any View reflecting the data set should refresh itself.
|
||||
*/
|
||||
public void notifyDataSetChanged() {
|
||||
mDataSetObservable.notifyChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the attached observers that the underlying data is no longer
|
||||
* valid or available. Once invoked this adapter is no longer valid and
|
||||
* should not report further data set changes.
|
||||
*/
|
||||
public void notifyDataSetInvalidated() {
|
||||
mDataSetObservable.notifyInvalidated();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package com.zftlive.android.view.fixedheadtable.adapter;
|
||||
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* The TableAdapter object acts as a bridge between an TableFixHeaders and the
|
||||
* underlying data for that view. The Adapter provides access to the data items.
|
||||
* The Adapter is also responsible for making a View for each item in the data
|
||||
* set.
|
||||
*
|
||||
* @author Brais Gab韓 (InQBarna)
|
||||
* @see TableFixHeaders
|
||||
*/
|
||||
public interface TableAdapter {
|
||||
|
||||
/**
|
||||
* An item view type that causes the AdapterView to ignore the item view.
|
||||
* For example, this can be used if the client does not want a particular
|
||||
* view to be given for conversion in
|
||||
* {@link #getView(int, int, View, ViewGroup)}.
|
||||
*
|
||||
* @see #getItemViewType(int, int)
|
||||
*/
|
||||
public final static int IGNORE_ITEM_VIEW_TYPE = -1;
|
||||
|
||||
/**
|
||||
* Register an observer that is called when changes happen to the data used
|
||||
* by this adapter.
|
||||
*
|
||||
* @param observer
|
||||
* the object that gets notified when the data set changes.
|
||||
*/
|
||||
void registerDataSetObserver(DataSetObserver observer);
|
||||
|
||||
/**
|
||||
* Unregister an observer that has previously been registered with this
|
||||
* adapter via {@link #registerDataSetObserver}.
|
||||
*
|
||||
* @param observer
|
||||
* the object to unregister.
|
||||
*/
|
||||
void unregisterDataSetObserver(DataSetObserver observer);
|
||||
|
||||
/**
|
||||
* How many rows are in the data table represented by this Adapter.
|
||||
*
|
||||
* @return count of rows.
|
||||
*/
|
||||
public int getRowCount();
|
||||
|
||||
/**
|
||||
* How many columns are in the data table represented by this Adapter.
|
||||
*
|
||||
* @return count of columns.
|
||||
*/
|
||||
public int getColumnCount();
|
||||
|
||||
/**
|
||||
* Get a View that displays the data at the specified row and column in the
|
||||
* data table. You can either create a View manually or inflate it from an
|
||||
* XML layout file.
|
||||
*
|
||||
* @param row
|
||||
* The row of the item within the adapter's data table of the
|
||||
* item whose view we want. If the row is <code>-1</code> it is
|
||||
* the header.
|
||||
* @param column
|
||||
* The column of the item within the adapter's data table of the
|
||||
* item whose view we want. If the column is <code>-1</code> it
|
||||
* is the header.
|
||||
* @param parent
|
||||
* The parent that this view will eventually be attached to.
|
||||
* @return A View corresponding to the data at the specified row and column.
|
||||
*/
|
||||
public View getView(int row, int column, View convertView, ViewGroup parent);
|
||||
|
||||
/**
|
||||
* Return the width of the column.
|
||||
*
|
||||
* @param column
|
||||
* the column. If the column is <code>-1</code> it is the header.
|
||||
* @return The width of the column, in pixels.
|
||||
*/
|
||||
public int getWidth(int column);
|
||||
|
||||
/**
|
||||
* Return the height of the row.
|
||||
*
|
||||
* @param row
|
||||
* the row. If the row is <code>-1</code> it is the header.
|
||||
* @return The height of the row, in pixels.
|
||||
*/
|
||||
public int getHeight(int row);
|
||||
|
||||
/**
|
||||
* Get the type of View that will be created by
|
||||
* {@link #getView(int, int, View, ViewGroup)} for the specified item.
|
||||
*
|
||||
* @param row
|
||||
* The row of the item within the adapter's data table of the
|
||||
* item whose view we want. If the row is <code>-1</code> it is
|
||||
* the header.
|
||||
* @param column
|
||||
* The column of the item within the adapter's data table of the
|
||||
* item whose view we want. If the column is <code>-1</code> it
|
||||
* is the header.
|
||||
* @return An integer representing the type of View. Two views should share
|
||||
* the same type if one can be converted to the other in
|
||||
* {@link #getView(int, int, View, ViewGroup)}). Note: Integers must
|
||||
* be in the range 0 to {@link #getViewTypeCount()} - 1.
|
||||
* {@link #IGNORE_ITEM_VIEW_TYPE} can also be returned.
|
||||
*/
|
||||
public int getItemViewType(int row, int column);
|
||||
|
||||
/**
|
||||
* Returns the number of types of Views that will be created by
|
||||
* {@link #getView(int, int, View, ViewGroup)}. Each type represents a set
|
||||
* of views that can be converted in
|
||||
* {@link #getView(int, int, View, ViewGroup)}. If the adapter always
|
||||
* returns the same type of View for all items, this method should return 1.
|
||||
*
|
||||
* This method will only be called when when the adapter is set on the the
|
||||
* AdapterView.
|
||||
*
|
||||
* @return The number of types of Views that will be created by this adapter
|
||||
*/
|
||||
public int getViewTypeCount();
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package com.zftlive.android.view.horizontalscrollview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.nineoldandroids.view.ViewHelper;
|
||||
|
||||
public class MyHorizontalScrollView extends HorizontalScrollView {
|
||||
|
||||
// 在HorizontalScrollView有个LinearLayout
|
||||
private LinearLayout linearLayout;
|
||||
// 菜单,内容页
|
||||
private ViewGroup myMenu;
|
||||
private ViewGroup myContent;
|
||||
//菜单宽度
|
||||
private int myMenuWidth;
|
||||
|
||||
// 屏幕宽度
|
||||
private int screenWidth;
|
||||
// 菜单与屏幕右侧的距离(dp)
|
||||
private int myMenuPaddingRight = 50;
|
||||
|
||||
// 避免多次调用onMeasure的标志
|
||||
private boolean once = false;
|
||||
|
||||
/**
|
||||
* 自定义View需要实现带有Context、AttributeSet这2个参数的构造方法,否则自定义参数会出错
|
||||
* 当使用了自定义属性时,会调用此构造方法
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
// 获取屏幕宽度
|
||||
WindowManager windowManager = (WindowManager) context
|
||||
.getSystemService(Context.WINDOW_SERVICE);
|
||||
DisplayMetrics outMetrics = new DisplayMetrics();
|
||||
windowManager.getDefaultDisplay().getMetrics(outMetrics);
|
||||
screenWidth = outMetrics.widthPixels;// 屏幕宽度
|
||||
|
||||
// 将dp转换px
|
||||
myMenuPaddingRight = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources()
|
||||
.getDisplayMetrics());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子View的宽高,决定自身View的宽高,每次启动都会调用此方法
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (!once) {//使其只调用一次
|
||||
// this指的是HorizontalScrollView,获取各个元素
|
||||
linearLayout = (LinearLayout) this.getChildAt(0);// 第一个子元素
|
||||
myMenu = (ViewGroup) linearLayout.getChildAt(0);// HorizontalScrollView下LinearLayout的第一个子元素
|
||||
myContent = (ViewGroup) linearLayout.getChildAt(1);// HorizontalScrollView下LinearLayout的第二个子元素
|
||||
|
||||
// 设置子View的宽高,高于屏幕一致
|
||||
myMenuWidth=myMenu.getLayoutParams().width = screenWidth - myMenuPaddingRight;// 菜单的宽度=屏幕宽度-右边距
|
||||
myContent.getLayoutParams().width = screenWidth;// 内容宽度=屏幕宽度
|
||||
// 决定自身View的宽高,高于屏幕一致
|
||||
// 由于这里的LinearLayout里只包含了Menu和Content所以就不需要额外的去指定自身的宽
|
||||
once = true;
|
||||
}
|
||||
}
|
||||
|
||||
//设置View的位置,首先,先将Menu隐藏(在eclipse中ScrollView的画面内容(非滚动条)正数表示向左移,向上移)
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
//刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
|
||||
if(changed){
|
||||
this.scrollTo(myMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
int action=ev.getAction();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
int scrollX=this.getScrollX();//滑动的距离scrollTo方法里,也就是onMeasure方法里的向左滑动那部分
|
||||
if(scrollX>=myMenuWidth/2){
|
||||
this.smoothScrollTo(myMenuWidth,0);//向左滑动展示内容
|
||||
}else{
|
||||
this.smoothScrollTo(0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
Log.i("tuzi",l+"");
|
||||
float scale = l * 1.0f / myMenuWidth; // 1 ~ 0
|
||||
float rightScale = 0.7f + 0.3f * scale;
|
||||
float leftScale = 1.0f - scale * 0.3f;
|
||||
float leftAlpha = 0.6f + 0.4f * (1 - scale);
|
||||
|
||||
// 调用属性动画,设置TranslationX
|
||||
ViewHelper.setTranslationX(myMenu, myMenuWidth * scale * 0.8f);
|
||||
|
||||
ViewHelper.setScaleX(myMenu, leftScale);
|
||||
ViewHelper.setScaleY(myMenu, leftScale);
|
||||
ViewHelper.setAlpha(myMenu, leftAlpha);
|
||||
// 设置内容缩放的中心点
|
||||
ViewHelper.setPivotX(myContent, 0);
|
||||
ViewHelper.setPivotY(myContent, myContent.getHeight() / 2);
|
||||
ViewHelper.setScaleX(myContent, rightScale);
|
||||
ViewHelper.setScaleY(myContent, rightScale);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
package com.zftlive.android.view.progressbar;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
public class RoundProgressBar extends View {
|
||||
|
||||
/** 画笔对象的引用*/
|
||||
private Paint paint;
|
||||
|
||||
/**当前进度*/
|
||||
private int progress;
|
||||
|
||||
/**进度条样式:空心*/
|
||||
public static final int STROKE = 0;
|
||||
|
||||
/**进度条样式:实心*/
|
||||
public static final int FILL = 1;
|
||||
|
||||
public RoundProgressBar(Context context) {
|
||||
this(context,null);
|
||||
}
|
||||
|
||||
public RoundProgressBar(Context context, AttributeSet attrs) {
|
||||
this(context, attrs,0);
|
||||
}
|
||||
|
||||
public RoundProgressBar(Context context, AttributeSet attrs,int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
paint = new Paint();
|
||||
|
||||
TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.RoundProgressBar);
|
||||
|
||||
//获取自定义属性和默认值
|
||||
roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);
|
||||
roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
|
||||
textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN);
|
||||
textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15);
|
||||
roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);
|
||||
max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
|
||||
textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);
|
||||
style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);
|
||||
|
||||
mTypedArray.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
/**
|
||||
* 画最外层的大圆环
|
||||
*/
|
||||
int centre = getWidth()/2; //获取圆心的x坐标
|
||||
int radius = (int) (centre - roundWidth/2); //圆环的半径
|
||||
paint.setColor(roundColor); //设置圆环的颜色
|
||||
paint.setStyle(Paint.Style.STROKE); //设置空心
|
||||
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
|
||||
paint.setAntiAlias(true); //消除锯齿
|
||||
canvas.drawCircle(centre, centre, radius, paint); //画出圆环
|
||||
|
||||
Log.e("log", centre + "");
|
||||
|
||||
/**
|
||||
* 画进度百分比
|
||||
*/
|
||||
paint.setStrokeWidth(0);
|
||||
paint.setColor(textColor);
|
||||
paint.setTextSize(textSize);
|
||||
paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
|
||||
int percent = (int)(((float)progress / (float)max) * 100); //中间的进度百分比,先转换成float在进行除法运算,不然都为0
|
||||
float textWidth = paint.measureText(percent + "%"); //测量字体宽度,我们需要根据字体的宽度设置在圆环中间
|
||||
|
||||
if(textIsDisplayable && percent != 0 && style == STROKE){
|
||||
canvas.drawText(percent + "%", centre - textWidth / 2, centre + textSize/2, paint); //画出进度百分比
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 画圆弧 ,画圆环的进度
|
||||
*/
|
||||
|
||||
//设置进度是实心还是空心
|
||||
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
|
||||
paint.setColor(roundProgressColor); //设置进度的颜色
|
||||
RectF oval = new RectF(centre - radius, centre - radius, centre
|
||||
+ radius, centre + radius); //用于定义的圆弧的形状和大小的界限
|
||||
|
||||
switch (style) {
|
||||
case STROKE:{
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
canvas.drawArc(oval, 0, 360 * progress / max, false, paint); //根据进度画圆弧
|
||||
break;
|
||||
}
|
||||
case FILL:{
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
if(progress !=0)
|
||||
canvas.drawArc(oval, 0, 360 * progress / max, true, paint); //根据进度画圆弧
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度条达到满的时候触发事件
|
||||
*/
|
||||
if(progress == max && null != onLoadFinish){
|
||||
onLoadFinish.onLoadFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度的最大值
|
||||
* @param max
|
||||
*/
|
||||
public synchronized void setMax(int max) {
|
||||
if(max < 0){
|
||||
throw new IllegalArgumentException("max not less than 0");
|
||||
}
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进度.需要同步
|
||||
* @return
|
||||
*/
|
||||
public synchronized int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
|
||||
* 刷新界面调用postInvalidate()能在非UI线程刷新
|
||||
* @param progress
|
||||
*/
|
||||
public synchronized void setProgress(int progress) {
|
||||
if(progress < 0){
|
||||
throw new IllegalArgumentException("progress not less than 0");
|
||||
}
|
||||
if(progress > max){
|
||||
progress = max;
|
||||
}
|
||||
if(progress <= max){
|
||||
this.progress = progress;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 一定一个接口
|
||||
*/
|
||||
public interface OnLoadFinishListener{
|
||||
public void onLoadFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化接口变量
|
||||
*/
|
||||
OnLoadFinishListener onLoadFinish = null;
|
||||
|
||||
/**
|
||||
* 自定义控件的自定义事件
|
||||
* @param listener 接口类型
|
||||
*/
|
||||
public void setOnLoadFinishListener(OnLoadFinishListener listener)
|
||||
{
|
||||
onLoadFinish = listener;
|
||||
}
|
||||
|
||||
/*********************************控件属性(开始)************************************/
|
||||
|
||||
/**
|
||||
* 圆环的颜色
|
||||
*/
|
||||
private int roundColor;
|
||||
|
||||
/**
|
||||
* 圆环进度的颜色
|
||||
*/
|
||||
private int roundProgressColor;
|
||||
|
||||
/**
|
||||
* 中间进度百分比的字符串的颜色
|
||||
*/
|
||||
private int textColor;
|
||||
|
||||
/**
|
||||
* 中间进度百分比的字符串的字体大小
|
||||
*/
|
||||
private float textSize;
|
||||
|
||||
/**
|
||||
* 圆环的宽度
|
||||
*/
|
||||
private float roundWidth;
|
||||
|
||||
/**
|
||||
* 最大进度
|
||||
*/
|
||||
private int max;
|
||||
|
||||
/**
|
||||
* 是否显示中间的进度
|
||||
*/
|
||||
private boolean textIsDisplayable;
|
||||
|
||||
/**
|
||||
* 进度的风格,实心或者空心
|
||||
*/
|
||||
private int style;
|
||||
|
||||
public int getCricleColor() {
|
||||
return roundColor;
|
||||
}
|
||||
|
||||
public void setCricleColor(int cricleColor) {
|
||||
this.roundColor = cricleColor;
|
||||
}
|
||||
|
||||
public int getCricleProgressColor() {
|
||||
return roundProgressColor;
|
||||
}
|
||||
|
||||
public void setCricleProgressColor(int cricleProgressColor) {
|
||||
this.roundProgressColor = cricleProgressColor;
|
||||
}
|
||||
|
||||
public int getTextColor() {
|
||||
return textColor;
|
||||
}
|
||||
|
||||
public void setTextColor(int textColor) {
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
public float getTextSize() {
|
||||
return textSize;
|
||||
}
|
||||
|
||||
public void setTextSize(float textSize) {
|
||||
this.textSize = textSize;
|
||||
}
|
||||
|
||||
public float getRoundWidth() {
|
||||
return roundWidth;
|
||||
}
|
||||
|
||||
public void setRoundWidth(float roundWidth) {
|
||||
this.roundWidth = roundWidth;
|
||||
}
|
||||
/*********************************控件属性(结束)************************************/
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.zftlive.android.view.progressbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
/**
|
||||
* 垂直进度条
|
||||
* @author 曾繁添
|
||||
*
|
||||
*/
|
||||
public class VerticalProgressBar extends ProgressBar
|
||||
{
|
||||
|
||||
public VerticalProgressBar(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public VerticalProgressBar(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public VerticalProgressBar(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDraw(Canvas canvas)
|
||||
{
|
||||
//反转90度,将水平ProgressBar竖起来
|
||||
canvas.rotate(-90);
|
||||
|
||||
//将经过旋转后得到的VerticalProgressBar移到正确的位置
|
||||
canvas.translate(-getHeight(), 0);
|
||||
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||
//互换宽高值
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
//互换宽高值
|
||||
super.onSizeChanged(h, w, oldw, oldh);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public interface ILoadingLayout {
|
||||
|
||||
/**
|
||||
* Set the Last Updated Text. This displayed under the main label when
|
||||
* Pulling
|
||||
*
|
||||
* @param label - Label to set
|
||||
*/
|
||||
public void setLastUpdatedLabel(CharSequence label);
|
||||
|
||||
/**
|
||||
* Set the drawable used in the loading layout. This is the same as calling
|
||||
* <code>setLoadingDrawable(drawable, Mode.BOTH)</code>
|
||||
*
|
||||
* @param drawable - Drawable to display
|
||||
*/
|
||||
public void setLoadingDrawable(Drawable drawable);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is being Pulled
|
||||
* <code>setPullLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param pullLabel - CharSequence to display
|
||||
*/
|
||||
public void setPullLabel(CharSequence pullLabel);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is refreshing
|
||||
* <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param refreshingLabel - CharSequence to display
|
||||
*/
|
||||
public void setRefreshingLabel(CharSequence refreshingLabel);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is being pulled, and will refresh when
|
||||
* released. This is the same as calling
|
||||
* <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param releaseLabel - CharSequence to display
|
||||
*/
|
||||
public void setReleaseLabel(CharSequence releaseLabel);
|
||||
|
||||
/**
|
||||
* Set's the Sets the typeface and style in which the text should be
|
||||
* displayed. Please see
|
||||
* {@link android.widget.TextView#setTypeface(Typeface)
|
||||
* TextView#setTypeface(Typeface)}.
|
||||
*/
|
||||
public void setTextTypeface(Typeface tf);
|
||||
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.OnPullEventListener;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.OnRefreshListener;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.OnRefreshListener2;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.State;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
public interface IPullToRefresh<T extends View> {
|
||||
|
||||
/**
|
||||
* Demos the Pull-to-Refresh functionality to the user so that they are
|
||||
* aware it is there. This could be useful when the user first opens your
|
||||
* app, etc. The animation will only happen if the Refresh View (ListView,
|
||||
* ScrollView, etc) is in a state where a Pull-to-Refresh could occur by a
|
||||
* user's touch gesture (i.e. scrolled to the top/bottom).
|
||||
*
|
||||
* @return true - if the Demo has been started, false if not.
|
||||
*/
|
||||
public boolean demo();
|
||||
|
||||
/**
|
||||
* Get the mode that this view is currently in. This is only really useful
|
||||
* when using <code>Mode.BOTH</code>.
|
||||
*
|
||||
* @return Mode that the view is currently in
|
||||
*/
|
||||
public Mode getCurrentMode();
|
||||
|
||||
/**
|
||||
* Returns whether the Touch Events are filtered or not. If true is
|
||||
* returned, then the View will only use touch events where the difference
|
||||
* in the Y-axis is greater than the difference in the X-axis. This means
|
||||
* that the View will not interfere when it is used in a horizontal
|
||||
* scrolling View (such as a ViewPager).
|
||||
*
|
||||
* @return boolean - true if the View is filtering Touch Events
|
||||
*/
|
||||
public boolean getFilterTouchEvents();
|
||||
|
||||
/**
|
||||
* Returns a proxy object which allows you to call methods on all of the
|
||||
* LoadingLayouts (the Views which show when Pulling/Refreshing).
|
||||
* <p />
|
||||
* You should not keep the result of this method any longer than you need
|
||||
* it.
|
||||
*
|
||||
* @return Object which will proxy any calls you make on it, to all of the
|
||||
* LoadingLayouts.
|
||||
*/
|
||||
public ILoadingLayout getLoadingLayoutProxy();
|
||||
|
||||
/**
|
||||
* Returns a proxy object which allows you to call methods on the
|
||||
* LoadingLayouts (the Views which show when Pulling/Refreshing). The actual
|
||||
* LoadingLayout(s) which will be affected, are chosen by the parameters you
|
||||
* give.
|
||||
* <p />
|
||||
* You should not keep the result of this method any longer than you need
|
||||
* it.
|
||||
*
|
||||
* @param includeStart - Whether to include the Start/Header Views
|
||||
* @param includeEnd - Whether to include the End/Footer Views
|
||||
* @return Object which will proxy any calls you make on it, to the
|
||||
* LoadingLayouts included.
|
||||
*/
|
||||
public ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd);
|
||||
|
||||
/**
|
||||
* Get the mode that this view has been set to. If this returns
|
||||
* <code>Mode.BOTH</code>, you can use <code>getCurrentMode()</code> to
|
||||
* check which mode the view is currently in
|
||||
*
|
||||
* @return Mode that the view has been set to
|
||||
*/
|
||||
public Mode getMode();
|
||||
|
||||
/**
|
||||
* Get the Wrapped Refreshable View. Anything returned here has already been
|
||||
* added to the content view.
|
||||
*
|
||||
* @return The View which is currently wrapped
|
||||
*/
|
||||
public T getRefreshableView();
|
||||
|
||||
/**
|
||||
* Get whether the 'Refreshing' View should be automatically shown when
|
||||
* refreshing. Returns true by default.
|
||||
*
|
||||
* @return - true if the Refreshing View will be show
|
||||
*/
|
||||
public boolean getShowViewWhileRefreshing();
|
||||
|
||||
/**
|
||||
* @return - The state that the View is currently in.
|
||||
*/
|
||||
public State getState();
|
||||
|
||||
/**
|
||||
* Whether Pull-to-Refresh is enabled
|
||||
*
|
||||
* @return enabled
|
||||
*/
|
||||
public boolean isPullToRefreshEnabled();
|
||||
|
||||
/**
|
||||
* Gets whether Overscroll support is enabled. This is different to
|
||||
* Android's standard Overscroll support (the edge-glow) which is available
|
||||
* from GINGERBREAD onwards
|
||||
*
|
||||
* @return true - if both PullToRefresh-OverScroll and Android's inbuilt
|
||||
* OverScroll are enabled
|
||||
*/
|
||||
public boolean isPullToRefreshOverScrollEnabled();
|
||||
|
||||
/**
|
||||
* Returns whether the Widget is currently in the Refreshing mState
|
||||
*
|
||||
* @return true if the Widget is currently refreshing
|
||||
*/
|
||||
public boolean isRefreshing();
|
||||
|
||||
/**
|
||||
* Returns whether the widget has enabled scrolling on the Refreshable View
|
||||
* while refreshing.
|
||||
*
|
||||
* @return true if the widget has enabled scrolling while refreshing
|
||||
*/
|
||||
public boolean isScrollingWhileRefreshingEnabled();
|
||||
|
||||
/**
|
||||
* Mark the current Refresh as complete. Will Reset the UI and hide the
|
||||
* Refreshing View
|
||||
*/
|
||||
public void onRefreshComplete();
|
||||
|
||||
/**
|
||||
* Set the Touch Events to be filtered or not. If set to true, then the View
|
||||
* will only use touch events where the difference in the Y-axis is greater
|
||||
* than the difference in the X-axis. This means that the View will not
|
||||
* interfere when it is used in a horizontal scrolling View (such as a
|
||||
* ViewPager), but will restrict which types of finger scrolls will trigger
|
||||
* the View.
|
||||
*
|
||||
* @param filterEvents - true if you want to filter Touch Events. Default is
|
||||
* true.
|
||||
*/
|
||||
public void setFilterTouchEvents(boolean filterEvents);
|
||||
|
||||
/**
|
||||
* Set the mode of Pull-to-Refresh that this view will use.
|
||||
*
|
||||
* @param mode - Mode to set the View to
|
||||
*/
|
||||
public void setMode(Mode mode);
|
||||
|
||||
/**
|
||||
* Set OnPullEventListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget has a pull event to
|
||||
* propogate.
|
||||
*/
|
||||
public void setOnPullEventListener(OnPullEventListener<T> listener);
|
||||
|
||||
/**
|
||||
* Set OnRefreshListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget is set to Refresh
|
||||
*/
|
||||
public void setOnRefreshListener(OnRefreshListener<T> listener);
|
||||
|
||||
/**
|
||||
* Set OnRefreshListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget is set to Refresh
|
||||
*/
|
||||
public void setOnRefreshListener(OnRefreshListener2<T> listener);
|
||||
|
||||
/**
|
||||
* Sets whether Overscroll support is enabled. This is different to
|
||||
* Android's standard Overscroll support (the edge-glow). This setting only
|
||||
* takes effect when running on device with Android v2.3 or greater.
|
||||
*
|
||||
* @param enabled - true if you want Overscroll enabled
|
||||
*/
|
||||
public void setPullToRefreshOverScrollEnabled(boolean enabled);
|
||||
|
||||
/**
|
||||
* Sets the Widget to be in the refresh state. The UI will be updated to
|
||||
* show the 'Refreshing' view, and be scrolled to show such.
|
||||
*/
|
||||
public void setRefreshing();
|
||||
|
||||
/**
|
||||
* Sets the Widget to be in the refresh state. The UI will be updated to
|
||||
* show the 'Refreshing' view.
|
||||
*
|
||||
* @param doScroll - true if you want to force a scroll to the Refreshing
|
||||
* view.
|
||||
*/
|
||||
public void setRefreshing(boolean doScroll);
|
||||
|
||||
/**
|
||||
* Sets the Animation Interpolator that is used for animated scrolling.
|
||||
* Defaults to a DecelerateInterpolator
|
||||
*
|
||||
* @param interpolator - Interpolator to use
|
||||
*/
|
||||
public void setScrollAnimationInterpolator(Interpolator interpolator);
|
||||
|
||||
/**
|
||||
* By default the Widget disables scrolling on the Refreshable View while
|
||||
* refreshing. This method can change this behaviour.
|
||||
*
|
||||
* @param scrollingWhileRefreshingEnabled - true if you want to enable
|
||||
* scrolling while refreshing
|
||||
*/
|
||||
public void setScrollingWhileRefreshingEnabled(boolean scrollingWhileRefreshingEnabled);
|
||||
|
||||
/**
|
||||
* A mutator to enable/disable whether the 'Refreshing' View should be
|
||||
* automatically shown when refreshing.
|
||||
*
|
||||
* @param showView
|
||||
*/
|
||||
public void setShowViewWhileRefreshing(boolean showView);
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.internal.LoadingLayout;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class LoadingLayoutProxy implements ILoadingLayout {
|
||||
|
||||
private final HashSet<LoadingLayout> mLoadingLayouts;
|
||||
|
||||
LoadingLayoutProxy() {
|
||||
mLoadingLayouts = new HashSet<LoadingLayout>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows you to add extra LoadingLayout instances to this proxy. This
|
||||
* is only necessary if you keep your own instances, and want to have them
|
||||
* included in any
|
||||
* {@link PullToRefreshBase#createLoadingLayoutProxy(boolean, boolean)
|
||||
* createLoadingLayoutProxy(...)} calls.
|
||||
*
|
||||
* @param layout - LoadingLayout to have included.
|
||||
*/
|
||||
public void addLayout(LoadingLayout layout) {
|
||||
if (null != layout) {
|
||||
mLoadingLayouts.add(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastUpdatedLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setLastUpdatedLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoadingDrawable(Drawable drawable) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setLoadingDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRefreshingLabel(CharSequence refreshingLabel) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setRefreshingLabel(refreshingLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPullLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setPullLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReleaseLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setReleaseLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextTypeface(Typeface tf) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setTextTypeface(tf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.State;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
@TargetApi(9)
|
||||
public final class OverscrollHelper {
|
||||
|
||||
static final String LOG_TAG = "OverscrollHelper";
|
||||
static final float DEFAULT_OVERSCROLL_SCALE = 1f;
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function.
|
||||
* <p/>
|
||||
* This should only be used on AdapterView's such as ListView as it just
|
||||
* calls through to overScrollBy() with the scrollRange = 0. AdapterView's
|
||||
* do not have a scroll range (i.e. getScrollY() doesn't work).
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final boolean isTouchEvent) {
|
||||
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, 0, isTouchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function. This version of the call is used for Views that need to specify
|
||||
* a Scroll Range but scroll back to it's edge correctly.
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param scrollRange - Scroll Range of the View, specifically needed for
|
||||
* ScrollView
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final int scrollRange, final boolean isTouchEvent) {
|
||||
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, scrollRange, 0, DEFAULT_OVERSCROLL_SCALE, isTouchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function. This is the advanced version of the call.
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param scrollRange - Scroll Range of the View, specifically needed for
|
||||
* ScrollView
|
||||
* @param fuzzyThreshold - Threshold for which the values how fuzzy we
|
||||
* should treat the other values. Needed for WebView as it
|
||||
* doesn't always scroll back to it's edge. 0 = no fuzziness.
|
||||
* @param scaleFactor - Scale Factor for overscroll amount
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final int scrollRange, final int fuzzyThreshold,
|
||||
final float scaleFactor, final boolean isTouchEvent) {
|
||||
|
||||
final int deltaValue, currentScrollValue, scrollValue;
|
||||
switch (view.getPullToRefreshScrollDirection()) {
|
||||
case HORIZONTAL:
|
||||
deltaValue = deltaX;
|
||||
scrollValue = scrollX;
|
||||
currentScrollValue = view.getScrollX();
|
||||
break;
|
||||
case VERTICAL:
|
||||
default:
|
||||
deltaValue = deltaY;
|
||||
scrollValue = scrollY;
|
||||
currentScrollValue = view.getScrollY();
|
||||
break;
|
||||
}
|
||||
|
||||
// Check that OverScroll is enabled and that we're not currently
|
||||
// refreshing.
|
||||
if (view.isPullToRefreshOverScrollEnabled() && !view.isRefreshing()) {
|
||||
final Mode mode = view.getMode();
|
||||
|
||||
// Check that Pull-to-Refresh is enabled, and the event isn't from
|
||||
// touch
|
||||
if (mode.permitsPullToRefresh() && !isTouchEvent && deltaValue != 0) {
|
||||
final int newScrollValue = (deltaValue + scrollValue);
|
||||
|
||||
if (PullToRefreshBase.DEBUG) {
|
||||
Log.d(LOG_TAG, "OverScroll. DeltaX: " + deltaX + ", ScrollX: " + scrollX + ", DeltaY: " + deltaY
|
||||
+ ", ScrollY: " + scrollY + ", NewY: " + newScrollValue + ", ScrollRange: " + scrollRange
|
||||
+ ", CurrentScroll: " + currentScrollValue);
|
||||
}
|
||||
|
||||
if (newScrollValue < (0 - fuzzyThreshold)) {
|
||||
// Check the mode supports the overscroll direction, and
|
||||
// then move scroll
|
||||
if (mode.showHeaderLoadingLayout()) {
|
||||
// If we're currently at zero, we're about to start
|
||||
// overscrolling, so change the state
|
||||
if (currentScrollValue == 0) {
|
||||
view.setState(State.OVERSCROLLING);
|
||||
}
|
||||
|
||||
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue)));
|
||||
}
|
||||
} else if (newScrollValue > (scrollRange + fuzzyThreshold)) {
|
||||
// Check the mode supports the overscroll direction, and
|
||||
// then move scroll
|
||||
if (mode.showFooterLoadingLayout()) {
|
||||
// If we're currently at zero, we're about to start
|
||||
// overscrolling, so change the state
|
||||
if (currentScrollValue == 0) {
|
||||
view.setState(State.OVERSCROLLING);
|
||||
}
|
||||
|
||||
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue - scrollRange)));
|
||||
}
|
||||
} else if (Math.abs(newScrollValue) <= fuzzyThreshold
|
||||
|| Math.abs(newScrollValue - scrollRange) <= fuzzyThreshold) {
|
||||
// Means we've stopped overscrolling, so scroll back to 0
|
||||
view.setState(State.RESET);
|
||||
}
|
||||
} else if (isTouchEvent && State.OVERSCROLLING == view.getState()) {
|
||||
// This condition means that we were overscrolling from a fling,
|
||||
// but the user has touched the View and is now overscrolling
|
||||
// from touch instead. We need to just reset.
|
||||
view.setState(State.RESET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAndroidOverScrollEnabled(View view) {
|
||||
return view.getOverScrollMode() != View.OVER_SCROLL_NEVER;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,476 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.internal.EmptyViewMethodAccessor;
|
||||
import com.zftlive.android.view.pulltorefresh.internal.IndicatorLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.OnScrollListener;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements
|
||||
OnScrollListener {
|
||||
|
||||
private static FrameLayout.LayoutParams convertEmptyViewLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
FrameLayout.LayoutParams newLp = null;
|
||||
|
||||
if (null != lp) {
|
||||
newLp = new FrameLayout.LayoutParams(lp);
|
||||
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
newLp.gravity = ((LinearLayout.LayoutParams) lp).gravity;
|
||||
} else {
|
||||
newLp.gravity = Gravity.CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
return newLp;
|
||||
}
|
||||
|
||||
private boolean mLastItemVisible;
|
||||
private OnScrollListener mOnScrollListener;
|
||||
private OnLastItemVisibleListener mOnLastItemVisibleListener;
|
||||
private View mEmptyView;
|
||||
|
||||
private IndicatorLayout mIndicatorIvTop;
|
||||
private IndicatorLayout mIndicatorIvBottom;
|
||||
|
||||
private boolean mShowIndicator;
|
||||
private boolean mScrollEmptyView = true;
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context) {
|
||||
super(context);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, Mode mode, AnimationStyle animStyle) {
|
||||
super(context, mode, animStyle);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether an indicator graphic should be displayed when the View is in
|
||||
* a state where a Pull-to-Refresh can happen. An example of this state is
|
||||
* when the Adapter View is scrolled to the top and the mode is set to
|
||||
* {@link Mode#PULL_FROM_START}. The default value is <var>true</var> if
|
||||
* {@link PullToRefreshBase#isPullToRefreshOverScrollEnabled()
|
||||
* isPullToRefreshOverScrollEnabled()} returns false.
|
||||
*
|
||||
* @return true if the indicators will be shown
|
||||
*/
|
||||
public boolean getShowIndicator() {
|
||||
return mShowIndicator;
|
||||
}
|
||||
|
||||
public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
|
||||
final int totalItemCount) {
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount
|
||||
+ ". Total Items:" + totalItemCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the Last Item is Visible. lastVisibleItemIndex is a
|
||||
* zero-based index, so we minus one totalItemCount to check
|
||||
*/
|
||||
if (null != mOnLastItemVisibleListener) {
|
||||
mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);
|
||||
}
|
||||
|
||||
// If we're showing the indicator, check positions...
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
|
||||
// Finally call OnScrollListener if we have one
|
||||
if (null != mOnScrollListener) {
|
||||
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
|
||||
}
|
||||
}
|
||||
|
||||
public final void onScrollStateChanged(final AbsListView view, final int state) {
|
||||
/**
|
||||
* Check that the scrolling has stopped, and that the last item is
|
||||
* visible.
|
||||
*/
|
||||
if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {
|
||||
mOnLastItemVisibleListener.onLastItemVisible();
|
||||
}
|
||||
|
||||
if (null != mOnScrollListener) {
|
||||
mOnScrollListener.onScrollStateChanged(view, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
|
||||
* getRefreshableView()}.
|
||||
* {@link AdapterView#setAdapter(android.widget.Adapter)}
|
||||
* setAdapter(adapter)}. This is just for convenience!
|
||||
*
|
||||
* @param adapter - Adapter to set
|
||||
*/
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
((AdapterView<ListAdapter>) mRefreshableView).setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Empty View to be used by the Adapter View.
|
||||
* <p/>
|
||||
* We need it handle it ourselves so that we can Pull-to-Refresh when the
|
||||
* Empty View is shown.
|
||||
* <p/>
|
||||
* Please note, you do <strong>not</strong> usually need to call this method
|
||||
* yourself. Calling setEmptyView on the AdapterView will automatically call
|
||||
* this method and set everything up. This includes when the Android
|
||||
* Framework automatically sets the Empty View based on it's ID.
|
||||
*
|
||||
* @param newEmptyView - Empty View to be used
|
||||
*/
|
||||
public final void setEmptyView(View newEmptyView) {
|
||||
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
|
||||
|
||||
if (null != newEmptyView) {
|
||||
// New view needs to be clickable so that Android recognizes it as a
|
||||
// target for Touch Events
|
||||
newEmptyView.setClickable(true);
|
||||
|
||||
ViewParent newEmptyViewParent = newEmptyView.getParent();
|
||||
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
|
||||
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
|
||||
}
|
||||
|
||||
// We need to convert any LayoutParams so that it works in our
|
||||
// FrameLayout
|
||||
FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
|
||||
if (null != lp) {
|
||||
refreshableViewWrapper.addView(newEmptyView, lp);
|
||||
} else {
|
||||
refreshableViewWrapper.addView(newEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
if (mRefreshableView instanceof EmptyViewMethodAccessor) {
|
||||
((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
|
||||
} else {
|
||||
mRefreshableView.setEmptyView(newEmptyView);
|
||||
}
|
||||
mEmptyView = newEmptyView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
|
||||
* getRefreshableView()}.
|
||||
* {@link AdapterView#setOnItemClickListener(OnItemClickListener)
|
||||
* setOnItemClickListener(listener)}. This is just for convenience!
|
||||
*
|
||||
* @param listener - OnItemClickListener to use
|
||||
*/
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
mRefreshableView.setOnItemClickListener(listener);
|
||||
}
|
||||
|
||||
public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
|
||||
mOnLastItemVisibleListener = listener;
|
||||
}
|
||||
|
||||
public final void setOnScrollListener(OnScrollListener listener) {
|
||||
mOnScrollListener = listener;
|
||||
}
|
||||
|
||||
public final void setScrollEmptyView(boolean doScroll) {
|
||||
mScrollEmptyView = doScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether an indicator graphic should be displayed when the View is in
|
||||
* a state where a Pull-to-Refresh can happen. An example of this state is
|
||||
* when the Adapter View is scrolled to the top and the mode is set to
|
||||
* {@link Mode#PULL_FROM_START}
|
||||
*
|
||||
* @param showIndicator - true if the indicators should be shown.
|
||||
*/
|
||||
public void setShowIndicator(boolean showIndicator) {
|
||||
mShowIndicator = showIndicator;
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
// If we're set to Show Indicator, add/update them
|
||||
addIndicatorViews();
|
||||
} else {
|
||||
// If not, then remove then
|
||||
removeIndicatorViews();
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
@Override
|
||||
protected void onPullToRefresh() {
|
||||
super.onPullToRefresh();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
switch (getCurrentMode()) {
|
||||
case PULL_FROM_END:
|
||||
mIndicatorIvBottom.pullToRefresh();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
mIndicatorIvTop.pullToRefresh();
|
||||
break;
|
||||
default:
|
||||
// NO-OP
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onRefreshing(boolean doScroll) {
|
||||
super.onRefreshing(doScroll);
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReleaseToRefresh() {
|
||||
super.onReleaseToRefresh();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
switch (getCurrentMode()) {
|
||||
case PULL_FROM_END:
|
||||
mIndicatorIvBottom.releaseToRefresh();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
mIndicatorIvTop.releaseToRefresh();
|
||||
break;
|
||||
default:
|
||||
// NO-OP
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleStyledAttributes(TypedArray a) {
|
||||
// Set Show Indicator to the XML value, or default value
|
||||
mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());
|
||||
}
|
||||
|
||||
protected boolean isReadyForPullStart() {
|
||||
return isFirstItemVisible();
|
||||
}
|
||||
|
||||
protected boolean isReadyForPullEnd() {
|
||||
return isLastItemVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
if (null != mEmptyView && !mScrollEmptyView) {
|
||||
mEmptyView.scrollTo(-l, -t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateUIForMode() {
|
||||
super.updateUIForMode();
|
||||
|
||||
// Check Indicator Views consistent with new Mode
|
||||
if (getShowIndicatorInternal()) {
|
||||
addIndicatorViews();
|
||||
} else {
|
||||
removeIndicatorViews();
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndicatorViews() {
|
||||
Mode mode = getMode();
|
||||
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
|
||||
|
||||
if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {
|
||||
// If the mode can pull down, and we don't have one set already
|
||||
mIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
|
||||
params.gravity = Gravity.TOP | Gravity.RIGHT;
|
||||
refreshableViewWrapper.addView(mIndicatorIvTop, params);
|
||||
|
||||
} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {
|
||||
// If we can't pull down, but have a View then remove it
|
||||
refreshableViewWrapper.removeView(mIndicatorIvTop);
|
||||
mIndicatorIvTop = null;
|
||||
}
|
||||
|
||||
if (mode.showFooterLoadingLayout() && null == mIndicatorIvBottom) {
|
||||
// If the mode can pull down, and we don't have one set already
|
||||
mIndicatorIvBottom = new IndicatorLayout(getContext(), Mode.PULL_FROM_END);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
|
||||
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
|
||||
refreshableViewWrapper.addView(mIndicatorIvBottom, params);
|
||||
|
||||
} else if (!mode.showFooterLoadingLayout() && null != mIndicatorIvBottom) {
|
||||
// If we can't pull down, but have a View then remove it
|
||||
refreshableViewWrapper.removeView(mIndicatorIvBottom);
|
||||
mIndicatorIvBottom = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getShowIndicatorInternal() {
|
||||
return mShowIndicator && isPullToRefreshEnabled();
|
||||
}
|
||||
|
||||
private boolean isFirstItemVisible() {
|
||||
final Adapter adapter = mRefreshableView.getAdapter();
|
||||
|
||||
if (null == adapter || adapter.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* This check should really just be:
|
||||
* mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
|
||||
* internally use a HeaderView which messes the positions up. For
|
||||
* now we'll just add one to account for it and rely on the inner
|
||||
* condition which checks getTop().
|
||||
*/
|
||||
if (mRefreshableView.getFirstVisiblePosition() <= 1) {
|
||||
final View firstVisibleChild = mRefreshableView.getChildAt(0);
|
||||
if (firstVisibleChild != null) {
|
||||
return firstVisibleChild.getTop() >= mRefreshableView.getTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLastItemVisible() {
|
||||
final Adapter adapter = mRefreshableView.getAdapter();
|
||||
|
||||
if (null == adapter || adapter.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
final int lastItemPosition = mRefreshableView.getCount() - 1;
|
||||
final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: "
|
||||
+ lastVisiblePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* This check should really just be: lastVisiblePosition ==
|
||||
* lastItemPosition, but PtRListView internally uses a FooterView
|
||||
* which messes the positions up. For me we'll just subtract one to
|
||||
* account for it and rely on the inner condition which checks
|
||||
* getBottom().
|
||||
*/
|
||||
if (lastVisiblePosition >= lastItemPosition - 1) {
|
||||
final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
|
||||
final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
|
||||
if (lastVisibleChild != null) {
|
||||
return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeIndicatorViews() {
|
||||
if (null != mIndicatorIvTop) {
|
||||
getRefreshableViewWrapper().removeView(mIndicatorIvTop);
|
||||
mIndicatorIvTop = null;
|
||||
}
|
||||
|
||||
if (null != mIndicatorIvBottom) {
|
||||
getRefreshableViewWrapper().removeView(mIndicatorIvBottom);
|
||||
mIndicatorIvBottom = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIndicatorViewsVisibility() {
|
||||
if (null != mIndicatorIvTop) {
|
||||
if (!isRefreshing() && isReadyForPullStart()) {
|
||||
if (!mIndicatorIvTop.isVisible()) {
|
||||
mIndicatorIvTop.show();
|
||||
}
|
||||
} else {
|
||||
if (mIndicatorIvTop.isVisible()) {
|
||||
mIndicatorIvTop.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null != mIndicatorIvBottom) {
|
||||
if (!isRefreshing() && isReadyForPullEnd()) {
|
||||
if (!mIndicatorIvBottom.isVisible()) {
|
||||
mIndicatorIvBottom.show();
|
||||
}
|
||||
} else {
|
||||
if (mIndicatorIvBottom.isVisible()) {
|
||||
mIndicatorIvBottom.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.internal.EmptyViewMethodAccessor;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
|
||||
public class PullToRefreshExpandableListView extends PullToRefreshAdapterViewBase<ExpandableListView> {
|
||||
|
||||
public PullToRefreshExpandableListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExpandableListView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
final ExpandableListView lv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
lv = new InternalExpandableListViewSDK9(context, attrs);
|
||||
} else {
|
||||
lv = new InternalExpandableListView(context, attrs);
|
||||
}
|
||||
|
||||
// Set it to this so it can be used in ListActivity/ListFragment
|
||||
lv.setId(android.R.id.list);
|
||||
return lv;
|
||||
}
|
||||
|
||||
class InternalExpandableListView extends ExpandableListView implements EmptyViewMethodAccessor {
|
||||
|
||||
public InternalExpandableListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshExpandableListView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalExpandableListViewSDK9 extends InternalExpandableListView {
|
||||
|
||||
public InternalExpandableListViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshExpandableListView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.internal.EmptyViewMethodAccessor;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.GridView;
|
||||
|
||||
public class PullToRefreshGridView extends PullToRefreshAdapterViewBase<GridView> {
|
||||
|
||||
public PullToRefreshGridView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final GridView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
final GridView gv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
gv = new InternalGridViewSDK9(context, attrs);
|
||||
} else {
|
||||
gv = new InternalGridView(context, attrs);
|
||||
}
|
||||
|
||||
// Use Generated ID (from res/values/ids.xml)
|
||||
gv.setId(R.id.gridview);
|
||||
return gv;
|
||||
}
|
||||
|
||||
class InternalGridView extends GridView implements EmptyViewMethodAccessor {
|
||||
|
||||
public InternalGridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshGridView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalGridViewSDK9 extends InternalGridView {
|
||||
|
||||
public InternalGridViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshGridView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
public class PullToRefreshHorizontalScrollView extends PullToRefreshBase<HorizontalScrollView> {
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HorizontalScrollView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
HorizontalScrollView scrollView;
|
||||
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
scrollView = new InternalHorizontalScrollViewSDK9(context, attrs);
|
||||
} else {
|
||||
scrollView = new HorizontalScrollView(context, attrs);
|
||||
}
|
||||
|
||||
scrollView.setId(R.id.scrollview);
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollX() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
View scrollViewChild = mRefreshableView.getChildAt(0);
|
||||
if (null != scrollViewChild) {
|
||||
return mRefreshableView.getScrollX() >= (scrollViewChild.getWidth() - getWidth());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalHorizontalScrollViewSDK9 extends HorizontalScrollView {
|
||||
|
||||
public InternalHorizontalScrollViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshHorizontalScrollView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from the AOSP ScrollView source
|
||||
*/
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
scrollRange = Math.max(0, child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()));
|
||||
}
|
||||
return scrollRange;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.internal.EmptyViewMethodAccessor;
|
||||
import com.zftlive.android.view.pulltorefresh.internal.LoadingLayout;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
|
||||
public class PullToRefreshListView extends PullToRefreshAdapterViewBase<ListView> {
|
||||
|
||||
private LoadingLayout mHeaderLoadingView;
|
||||
private LoadingLayout mFooterLoadingView;
|
||||
|
||||
private FrameLayout mLvFooterLoadingFrame;
|
||||
|
||||
private boolean mListViewExtrasEnabled;
|
||||
|
||||
public PullToRefreshListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRefreshing(final boolean doScroll) {
|
||||
/**
|
||||
* If we're not showing the Refreshing view, or the list is empty, the
|
||||
* the header/footer views won't show so we use the normal method.
|
||||
*/
|
||||
ListAdapter adapter = mRefreshableView.getAdapter();
|
||||
if (!mListViewExtrasEnabled || !getShowViewWhileRefreshing() || null == adapter || adapter.isEmpty()) {
|
||||
super.onRefreshing(doScroll);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onRefreshing(false);
|
||||
|
||||
final LoadingLayout origLoadingView, listViewLoadingView, oppositeListViewLoadingView;
|
||||
final int selection, scrollToY;
|
||||
|
||||
switch (getCurrentMode()) {
|
||||
case MANUAL_REFRESH_ONLY:
|
||||
case PULL_FROM_END:
|
||||
origLoadingView = getFooterLayout();
|
||||
listViewLoadingView = mFooterLoadingView;
|
||||
oppositeListViewLoadingView = mHeaderLoadingView;
|
||||
selection = mRefreshableView.getCount() - 1;
|
||||
scrollToY = getScrollY() - getFooterSize();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
origLoadingView = getHeaderLayout();
|
||||
listViewLoadingView = mHeaderLoadingView;
|
||||
oppositeListViewLoadingView = mFooterLoadingView;
|
||||
selection = 0;
|
||||
scrollToY = getScrollY() + getHeaderSize();
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide our original Loading View
|
||||
origLoadingView.reset();
|
||||
origLoadingView.hideAllViews();
|
||||
|
||||
// Make sure the opposite end is hidden too
|
||||
oppositeListViewLoadingView.setVisibility(View.GONE);
|
||||
|
||||
// Show the ListView Loading View and set it to refresh.
|
||||
listViewLoadingView.setVisibility(View.VISIBLE);
|
||||
listViewLoadingView.refreshing();
|
||||
|
||||
if (doScroll) {
|
||||
// We need to disable the automatic visibility changes for now
|
||||
disableLoadingLayoutVisibilityChanges();
|
||||
|
||||
// We scroll slightly so that the ListView's header/footer is at the
|
||||
// same Y position as our normal header/footer
|
||||
setHeaderScroll(scrollToY);
|
||||
|
||||
// Make sure the ListView is scrolled to show the loading
|
||||
// header/footer
|
||||
mRefreshableView.setSelection(selection);
|
||||
|
||||
// Smooth scroll as normal
|
||||
smoothScrollTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
/**
|
||||
* If the extras are not enabled, just call up to super and return.
|
||||
*/
|
||||
if (!mListViewExtrasEnabled) {
|
||||
super.onReset();
|
||||
return;
|
||||
}
|
||||
|
||||
final LoadingLayout originalLoadingLayout, listViewLoadingLayout;
|
||||
final int scrollToHeight, selection;
|
||||
final boolean scrollLvToEdge;
|
||||
|
||||
switch (getCurrentMode()) {
|
||||
case MANUAL_REFRESH_ONLY:
|
||||
case PULL_FROM_END:
|
||||
originalLoadingLayout = getFooterLayout();
|
||||
listViewLoadingLayout = mFooterLoadingView;
|
||||
selection = mRefreshableView.getCount() - 1;
|
||||
scrollToHeight = getFooterSize();
|
||||
scrollLvToEdge = Math.abs(mRefreshableView.getLastVisiblePosition() - selection) <= 1;
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
originalLoadingLayout = getHeaderLayout();
|
||||
listViewLoadingLayout = mHeaderLoadingView;
|
||||
scrollToHeight = -getHeaderSize();
|
||||
selection = 0;
|
||||
scrollLvToEdge = Math.abs(mRefreshableView.getFirstVisiblePosition() - selection) <= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the ListView header loading layout is showing, then we need to
|
||||
// flip so that the original one is showing instead
|
||||
if (listViewLoadingLayout.getVisibility() == View.VISIBLE) {
|
||||
|
||||
// Set our Original View to Visible
|
||||
originalLoadingLayout.showInvisibleViews();
|
||||
|
||||
// Hide the ListView Header/Footer
|
||||
listViewLoadingLayout.setVisibility(View.GONE);
|
||||
|
||||
/**
|
||||
* Scroll so the View is at the same Y as the ListView
|
||||
* header/footer, but only scroll if: we've pulled to refresh, it's
|
||||
* positioned correctly
|
||||
*/
|
||||
if (scrollLvToEdge && getState() != State.MANUAL_REFRESHING) {
|
||||
mRefreshableView.setSelection(selection);
|
||||
setHeaderScroll(scrollToHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, call up to super
|
||||
super.onReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart, final boolean includeEnd) {
|
||||
LoadingLayoutProxy proxy = super.createLoadingLayoutProxy(includeStart, includeEnd);
|
||||
|
||||
if (mListViewExtrasEnabled) {
|
||||
final Mode mode = getMode();
|
||||
|
||||
if (includeStart && mode.showHeaderLoadingLayout()) {
|
||||
proxy.addLayout(mHeaderLoadingView);
|
||||
}
|
||||
if (includeEnd && mode.showFooterLoadingLayout()) {
|
||||
proxy.addLayout(mFooterLoadingView);
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
protected ListView createListView(Context context, AttributeSet attrs) {
|
||||
final ListView lv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
lv = new InternalListViewSDK9(context, attrs);
|
||||
} else {
|
||||
lv = new InternalListView(context, attrs);
|
||||
}
|
||||
return lv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
ListView lv = createListView(context, attrs);
|
||||
|
||||
// Set it to this so it can be used in ListActivity/ListFragment
|
||||
lv.setId(android.R.id.list);
|
||||
return lv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleStyledAttributes(TypedArray a) {
|
||||
super.handleStyledAttributes(a);
|
||||
|
||||
mListViewExtrasEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrListViewExtrasEnabled, true);
|
||||
|
||||
if (mListViewExtrasEnabled) {
|
||||
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
|
||||
|
||||
// Create Loading Views ready for use later
|
||||
FrameLayout frame = new FrameLayout(getContext());
|
||||
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
|
||||
mHeaderLoadingView.setVisibility(View.GONE);
|
||||
frame.addView(mHeaderLoadingView, lp);
|
||||
mRefreshableView.addHeaderView(frame, null, false);
|
||||
|
||||
mLvFooterLoadingFrame = new FrameLayout(getContext());
|
||||
mFooterLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_END, a);
|
||||
mFooterLoadingView.setVisibility(View.GONE);
|
||||
mLvFooterLoadingFrame.addView(mFooterLoadingView, lp);
|
||||
|
||||
/**
|
||||
* If the value for Scrolling While Refreshing hasn't been
|
||||
* explicitly set via XML, enable Scrolling While Refreshing.
|
||||
*/
|
||||
if (!a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
|
||||
setScrollingWhileRefreshingEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalListViewSDK9 extends InternalListView {
|
||||
|
||||
public InternalListViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshListView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
protected class InternalListView extends ListView implements EmptyViewMethodAccessor {
|
||||
|
||||
private boolean mAddedLvFooter = false;
|
||||
|
||||
public InternalListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
/**
|
||||
* This is a bit hacky, but Samsung's ListView has got a bug in it
|
||||
* when using Header/Footer Views and the list is empty. This masks
|
||||
* the issue so that it doesn't cause an FC. See Issue #66.
|
||||
*/
|
||||
try {
|
||||
super.dispatchDraw(canvas);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
/**
|
||||
* This is a bit hacky, but Samsung's ListView has got a bug in it
|
||||
* when using Header/Footer Views and the list is empty. This masks
|
||||
* the issue so that it doesn't cause an FC. See Issue #66.
|
||||
*/
|
||||
try {
|
||||
return super.dispatchTouchEvent(ev);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
// Add the Footer View at the last possible moment
|
||||
if (null != mLvFooterLoadingFrame && !mAddedLvFooter) {
|
||||
addFooterView(mLvFooterLoadingFrame, null, false);
|
||||
mAddedLvFooter = true;
|
||||
}
|
||||
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshListView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class PullToRefreshScrollView extends PullToRefreshBase<ScrollView> {
|
||||
|
||||
public PullToRefreshScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
ScrollView scrollView;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
scrollView = new InternalScrollViewSDK9(context, attrs);
|
||||
} else {
|
||||
scrollView = new ScrollView(context, attrs);
|
||||
}
|
||||
|
||||
scrollView.setId(R.id.scrollview);
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollY() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
View scrollViewChild = mRefreshableView.getChildAt(0);
|
||||
if (null != scrollViewChild) {
|
||||
return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalScrollViewSDK9 extends ScrollView {
|
||||
|
||||
public InternalScrollViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from the AOSP ScrollView source
|
||||
*/
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||
}
|
||||
return scrollRange;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatMath;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||
|
||||
private static final OnRefreshListener<WebView> defaultOnRefreshListener = new OnRefreshListener<WebView>() {
|
||||
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<WebView> refreshView) {
|
||||
refreshView.getRefreshableView().reload();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final WebChromeClient defaultWebChromeClient = new WebChromeClient() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress == 100) {
|
||||
onRefreshComplete();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public PullToRefreshWebView(Context context) {
|
||||
super(context);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
WebView webView;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
webView = new InternalWebViewSDK9(context, attrs);
|
||||
} else {
|
||||
webView = new WebView(context, attrs);
|
||||
}
|
||||
|
||||
webView.setId(R.id.webview);
|
||||
return webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollY() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
float exactContentHeight = FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
|
||||
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onPtrRestoreInstanceState(savedInstanceState);
|
||||
mRefreshableView.restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPtrSaveInstanceState(Bundle saveState) {
|
||||
super.onPtrSaveInstanceState(saveState);
|
||||
mRefreshableView.saveState(saveState);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalWebViewSDK9 extends WebView {
|
||||
|
||||
// WebView doesn't always scroll back to it's edge so we add some
|
||||
// fuzziness
|
||||
static final int OVERSCROLL_FUZZY_THRESHOLD = 2;
|
||||
|
||||
// WebView seems quite reluctant to overscroll so we use the scale
|
||||
// factor to scale it's value
|
||||
static final float OVERSCROLL_SCALE_FACTOR = 1.5f;
|
||||
|
||||
public InternalWebViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshWebView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), OVERSCROLL_FUZZY_THRESHOLD, OVERSCROLL_SCALE_FACTOR, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private int getScrollRange() {
|
||||
return (int) Math.max(0, FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
|
||||
- (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshWebView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* An advanced version of {@link PullToRefreshWebView} which delegates the
|
||||
* triggering of the PullToRefresh gesture to the Javascript running within the
|
||||
* WebView. This means that you should only use this class if:
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>{@link PullToRefreshWebView} doesn't work correctly because you're using
|
||||
* <code>overflow:scroll</code> or something else which means
|
||||
* {@link WebView#getScrollY()} doesn't return correct values.</li>
|
||||
* <li>You control the web content being displayed, as you need to write some
|
||||
* Javascript callbacks.</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* <p/>
|
||||
* The way this call works is that when a PullToRefresh gesture is in action,
|
||||
* the following Javascript methods will be called:
|
||||
* <code>isReadyForPullDown()</code> and <code>isReadyForPullUp()</code>, it is
|
||||
* your job to calculate whether the view is in a state where a PullToRefresh
|
||||
* can happen, and return the result via the callback mechanism. An example can
|
||||
* be seen below:
|
||||
* <p/>
|
||||
*
|
||||
* <pre>
|
||||
* function isReadyForPullDown() {
|
||||
* var result = ... // Probably using the .scrollTop DOM attribute
|
||||
* ptr.isReadyForPullDownResponse(result);
|
||||
* }
|
||||
*
|
||||
* function isReadyForPullUp() {
|
||||
* var result = ... // Probably using the .scrollBottom DOM attribute
|
||||
* ptr.isReadyForPullUpResponse(result);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Chris Banes
|
||||
*/
|
||||
public class PullToRefreshWebView2 extends PullToRefreshWebView {
|
||||
|
||||
static final String JS_INTERFACE_PKG = "ptr";
|
||||
static final String DEF_JS_READY_PULL_DOWN_CALL = "javascript:isReadyForPullDown();";
|
||||
static final String DEF_JS_READY_PULL_UP_CALL = "javascript:isReadyForPullUp();";
|
||||
|
||||
public PullToRefreshWebView2(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView2(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView2(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
private JsValueCallback mJsCallback;
|
||||
private final AtomicBoolean mIsReadyForPullDown = new AtomicBoolean(false);
|
||||
private final AtomicBoolean mIsReadyForPullUp = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
protected WebView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
WebView webView = super.createRefreshableView(context, attrs);
|
||||
|
||||
// Need to add JS Interface so we can get the response back
|
||||
mJsCallback = new JsValueCallback();
|
||||
webView.addJavascriptInterface(mJsCallback, JS_INTERFACE_PKG);
|
||||
|
||||
return webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
// Call Javascript...
|
||||
getRefreshableView().loadUrl(DEF_JS_READY_PULL_DOWN_CALL);
|
||||
|
||||
// Response will be given to JsValueCallback, which will update
|
||||
// mIsReadyForPullDown
|
||||
|
||||
return mIsReadyForPullDown.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
// Call Javascript...
|
||||
getRefreshableView().loadUrl(DEF_JS_READY_PULL_UP_CALL);
|
||||
|
||||
// Response will be given to JsValueCallback, which will update
|
||||
// mIsReadyForPullUp
|
||||
|
||||
return mIsReadyForPullUp.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for response from Javascript
|
||||
*
|
||||
* @author Chris Banes
|
||||
*/
|
||||
final class JsValueCallback {
|
||||
|
||||
public void isReadyForPullUpResponse(boolean response) {
|
||||
mIsReadyForPullUp.set(response);
|
||||
}
|
||||
|
||||
public void isReadyForPullDownResponse(boolean response) {
|
||||
mIsReadyForPullDown.set(response);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.State;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.view.View;
|
||||
|
||||
public class SoundPullEventListener<V extends View> implements PullToRefreshBase.OnPullEventListener<V> {
|
||||
|
||||
private final Context mContext;
|
||||
private final HashMap<State, Integer> mSoundMap;
|
||||
|
||||
private MediaPlayer mCurrentMediaPlayer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context - Context
|
||||
*/
|
||||
public SoundPullEventListener(Context context) {
|
||||
mContext = context;
|
||||
mSoundMap = new HashMap<State, Integer>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPullEvent(PullToRefreshBase<V> refreshView, State event, Mode direction) {
|
||||
Integer soundResIdObj = mSoundMap.get(event);
|
||||
if (null != soundResIdObj) {
|
||||
playSound(soundResIdObj.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Sounds to be played when a Pull Event happens. You specify which
|
||||
* sound plays for which events by calling this method multiple times for
|
||||
* each event.
|
||||
* <p/>
|
||||
* If you've already set a sound for a certain event, and add another sound
|
||||
* for that event, only the new sound will be played.
|
||||
*
|
||||
* @param event - The event for which the sound will be played.
|
||||
* @param resId - Resource Id of the sound file to be played (e.g.
|
||||
* <var>R.raw.pull_sound</var>)
|
||||
*/
|
||||
public void addSoundEvent(State event, int resId) {
|
||||
mSoundMap.put(event, resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all of the previously set sounds and events.
|
||||
*/
|
||||
public void clearSounds() {
|
||||
mSoundMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current (or last) MediaPlayer instance.
|
||||
*/
|
||||
public MediaPlayer getCurrentMediaPlayer() {
|
||||
return mCurrentMediaPlayer;
|
||||
}
|
||||
|
||||
private void playSound(int resId) {
|
||||
// Stop current player, if there's one playing
|
||||
if (null != mCurrentMediaPlayer) {
|
||||
mCurrentMediaPlayer.stop();
|
||||
mCurrentMediaPlayer.release();
|
||||
}
|
||||
|
||||
mCurrentMediaPlayer = MediaPlayer.create(mContext, resId);
|
||||
if (null != mCurrentMediaPlayer) {
|
||||
mCurrentMediaPlayer.start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras.listfragment;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
abstract class PullToRefreshBaseListFragment<T extends PullToRefreshBase<? extends AbsListView>> extends ListFragment {
|
||||
|
||||
private T mPullToRefreshListView;
|
||||
|
||||
@Override
|
||||
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View layout = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
ListView lv = (ListView) layout.findViewById(android.R.id.list);
|
||||
ViewGroup parent = (ViewGroup) lv.getParent();
|
||||
|
||||
// Remove ListView and add PullToRefreshListView in its place
|
||||
int lvIndex = parent.indexOfChild(lv);
|
||||
parent.removeViewAt(lvIndex);
|
||||
mPullToRefreshListView = onCreatePullToRefreshListView(inflater, savedInstanceState);
|
||||
parent.addView(mPullToRefreshListView, lvIndex, lv.getLayoutParams());
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link PullToRefreshBase} attached to this ListFragment.
|
||||
*/
|
||||
public final T getPullToRefreshListView() {
|
||||
return mPullToRefreshListView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PullToRefreshBase} which will replace the ListView
|
||||
* created from ListFragment. You should override this method if you wish to
|
||||
* customise the {@link PullToRefreshBase} from the default.
|
||||
*
|
||||
* @param inflater - LayoutInflater which can be used to inflate from XML.
|
||||
* @param savedInstanceState - Bundle passed through from
|
||||
* {@link ListFragment#onCreateView(LayoutInflater, ViewGroup, Bundle)
|
||||
* onCreateView(...)}
|
||||
* @return The {@link PullToRefreshBase} which will replace the ListView.
|
||||
*/
|
||||
protected abstract T onCreatePullToRefreshListView(LayoutInflater inflater, Bundle savedInstanceState);
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras.listfragment;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshExpandableListView;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
|
||||
/**
|
||||
* A sample implementation of how to use {@link PullToRefreshExpandableListView}
|
||||
* with {@link ListFragment}. This implementation simply replaces the ListView
|
||||
* that {@code ListFragment} creates with a new
|
||||
* {@code PullToRefreshExpandableListView}. This means that ListFragment still
|
||||
* works 100% (e.g. <code>setListShown(...)</code> ).
|
||||
* <p/>
|
||||
* The new PullToRefreshListView is created in the method
|
||||
* {@link #onCreatePullToRefreshListView(LayoutInflater, Bundle)}. If you wish
|
||||
* to customise the {@code PullToRefreshExpandableListView} then override this
|
||||
* method and return your customised instance.
|
||||
*
|
||||
* @author Chris Banes
|
||||
*
|
||||
*/
|
||||
public class PullToRefreshExpandableListFragment extends PullToRefreshBaseListFragment<PullToRefreshExpandableListView> {
|
||||
|
||||
protected PullToRefreshExpandableListView onCreatePullToRefreshListView(LayoutInflater inflater,
|
||||
Bundle savedInstanceState) {
|
||||
return new PullToRefreshExpandableListView(getActivity());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras.listfragment;
|
||||
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshListView;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
/**
|
||||
* A sample implementation of how to use {@link PullToRefreshListView} with
|
||||
* {@link ListFragment}. This implementation simply replaces the ListView that
|
||||
* {@code ListFragment} creates with a new PullToRefreshListView. This means
|
||||
* that ListFragment still works 100% (e.g. <code>setListShown(...)</code> ).
|
||||
* <p/>
|
||||
* The new PullToRefreshListView is created in the method
|
||||
* {@link #onCreatePullToRefreshListView(LayoutInflater, Bundle)}. If you wish
|
||||
* to customise the {@code PullToRefreshListView} then override this method and
|
||||
* return your customised instance.
|
||||
*
|
||||
* @author Chris Banes
|
||||
*
|
||||
*/
|
||||
public class PullToRefreshListFragment extends PullToRefreshBaseListFragment<PullToRefreshListView> {
|
||||
|
||||
protected PullToRefreshListView onCreatePullToRefreshListView(LayoutInflater inflater, Bundle savedInstanceState) {
|
||||
return new PullToRefreshListView(getActivity());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.extras.viewpager;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class PullToRefreshViewPager extends PullToRefreshBase<ViewPager> {
|
||||
|
||||
public PullToRefreshViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewPager createRefreshableView(Context context, AttributeSet attrs) {
|
||||
ViewPager viewPager = new ViewPager(context, attrs);
|
||||
viewPager.setId(R.id.viewpager);
|
||||
return viewPager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
ViewPager refreshableView = getRefreshableView();
|
||||
|
||||
PagerAdapter adapter = refreshableView.getAdapter();
|
||||
if (null != adapter) {
|
||||
return refreshableView.getCurrentItem() == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
ViewPager refreshableView = getRefreshableView();
|
||||
|
||||
PagerAdapter adapter = refreshableView.getAdapter();
|
||||
if (null != adapter) {
|
||||
return refreshableView.getCurrentItem() == adapter.getCount() - 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Interface that allows PullToRefreshBase to hijack the call to
|
||||
* AdapterView.setEmptyView()
|
||||
*
|
||||
* @author chris
|
||||
*/
|
||||
public interface EmptyViewMethodAccessor {
|
||||
|
||||
/**
|
||||
* Calls upto AdapterView.setEmptyView()
|
||||
*
|
||||
* @param emptyView - to set as Empty View
|
||||
*/
|
||||
public void setEmptyViewInternal(View emptyView);
|
||||
|
||||
/**
|
||||
* Should call PullToRefreshBase.setEmptyView() which will then
|
||||
* automatically call through to setEmptyViewInternal()
|
||||
*
|
||||
* @param emptyView - to set as Empty View
|
||||
*/
|
||||
public void setEmptyView(View emptyView);
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Orientation;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class FlipLoadingLayout extends LoadingLayout {
|
||||
|
||||
static final int FLIP_ANIMATION_DURATION = 150;
|
||||
|
||||
private final Animation mRotateAnimation, mResetRotateAnimation;
|
||||
|
||||
public FlipLoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
|
||||
super(context, mode, scrollDirection, attrs);
|
||||
|
||||
final int rotateAngle = mode == Mode.PULL_FROM_START ? -180 : 180;
|
||||
|
||||
mRotateAnimation = new RotateAnimation(0, rotateAngle, Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
|
||||
mRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
|
||||
mRotateAnimation.setFillAfter(true);
|
||||
|
||||
mResetRotateAnimation = new RotateAnimation(rotateAngle, 0, Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
mResetRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
|
||||
mResetRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
|
||||
mResetRotateAnimation.setFillAfter(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoadingDrawableSet(Drawable imageDrawable) {
|
||||
if (null != imageDrawable) {
|
||||
final int dHeight = imageDrawable.getIntrinsicHeight();
|
||||
final int dWidth = imageDrawable.getIntrinsicWidth();
|
||||
|
||||
/**
|
||||
* We need to set the width/height of the ImageView so that it is
|
||||
* square with each side the size of the largest drawable dimension.
|
||||
* This is so that it doesn't clip when rotated.
|
||||
*/
|
||||
ViewGroup.LayoutParams lp = mHeaderImage.getLayoutParams();
|
||||
lp.width = lp.height = Math.max(dHeight, dWidth);
|
||||
mHeaderImage.requestLayout();
|
||||
|
||||
/**
|
||||
* We now rotate the Drawable so that is at the correct rotation,
|
||||
* and is centered.
|
||||
*/
|
||||
mHeaderImage.setScaleType(ScaleType.MATRIX);
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postTranslate((lp.width - dWidth) / 2f, (lp.height - dHeight) / 2f);
|
||||
matrix.postRotate(getDrawableRotationAngle(), lp.width / 2f, lp.height / 2f);
|
||||
mHeaderImage.setImageMatrix(matrix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPullImpl(float scaleOfLayout) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pullToRefreshImpl() {
|
||||
// Only start reset Animation, we've previously show the rotate anim
|
||||
if (mRotateAnimation == mHeaderImage.getAnimation()) {
|
||||
mHeaderImage.startAnimation(mResetRotateAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshingImpl() {
|
||||
mHeaderImage.clearAnimation();
|
||||
mHeaderImage.setVisibility(View.INVISIBLE);
|
||||
mHeaderProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseToRefreshImpl() {
|
||||
mHeaderImage.startAnimation(mRotateAnimation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImpl() {
|
||||
mHeaderImage.clearAnimation();
|
||||
mHeaderProgress.setVisibility(View.GONE);
|
||||
mHeaderImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultDrawableResId() {
|
||||
return R.drawable.view_pull_refresh_default_ptr_flip;
|
||||
}
|
||||
|
||||
private float getDrawableRotationAngle() {
|
||||
float angle = 0f;
|
||||
switch (mMode) {
|
||||
case PULL_FROM_END:
|
||||
if (mScrollDirection == Orientation.HORIZONTAL) {
|
||||
angle = 90f;
|
||||
} else {
|
||||
angle = 180f;
|
||||
}
|
||||
break;
|
||||
|
||||
case PULL_FROM_START:
|
||||
if (mScrollDirection == Orientation.HORIZONTAL) {
|
||||
angle = 270f;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class IndicatorLayout extends FrameLayout implements AnimationListener {
|
||||
|
||||
static final int DEFAULT_ROTATION_ANIMATION_DURATION = 150;
|
||||
|
||||
private Animation mInAnim, mOutAnim;
|
||||
private ImageView mArrowImageView;
|
||||
|
||||
private final Animation mRotateAnimation, mResetRotateAnimation;
|
||||
|
||||
public IndicatorLayout(Context context, PullToRefreshBase.Mode mode) {
|
||||
super(context);
|
||||
mArrowImageView = new ImageView(context);
|
||||
|
||||
Drawable arrowD = getResources().getDrawable(R.drawable.view_pull_refresh_indicator_arrow);
|
||||
mArrowImageView.setImageDrawable(arrowD);
|
||||
|
||||
final int padding = getResources().getDimensionPixelSize(R.dimen.indicator_internal_padding);
|
||||
mArrowImageView.setPadding(padding, padding, padding, padding);
|
||||
addView(mArrowImageView);
|
||||
|
||||
int inAnimResId, outAnimResId;
|
||||
switch (mode) {
|
||||
case PULL_FROM_END:
|
||||
inAnimResId = R.anim.view_pull_refresh_slide_in_from_bottom;
|
||||
outAnimResId = R.anim.view_pull_refresh_slide_out_to_bottom;
|
||||
setBackgroundResource(R.drawable.view_pull_refresh_indicator_bg_bottom);
|
||||
|
||||
// Rotate Arrow so it's pointing the correct way
|
||||
mArrowImageView.setScaleType(ScaleType.MATRIX);
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(180f, arrowD.getIntrinsicWidth() / 2f, arrowD.getIntrinsicHeight() / 2f);
|
||||
mArrowImageView.setImageMatrix(matrix);
|
||||
break;
|
||||
default:
|
||||
case PULL_FROM_START:
|
||||
inAnimResId = R.anim.view_pull_refresh_slide_in_from_top;
|
||||
outAnimResId = R.anim.view_pull_refresh_slide_out_to_top;
|
||||
setBackgroundResource(R.drawable.view_pull_refresh_indicator_bg_top);
|
||||
break;
|
||||
}
|
||||
|
||||
mInAnim = AnimationUtils.loadAnimation(context, inAnimResId);
|
||||
mInAnim.setAnimationListener(this);
|
||||
|
||||
mOutAnim = AnimationUtils.loadAnimation(context, outAnimResId);
|
||||
mOutAnim.setAnimationListener(this);
|
||||
|
||||
final Interpolator interpolator = new LinearInterpolator();
|
||||
mRotateAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
|
||||
0.5f);
|
||||
mRotateAnimation.setInterpolator(interpolator);
|
||||
mRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
|
||||
mRotateAnimation.setFillAfter(true);
|
||||
|
||||
mResetRotateAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
mResetRotateAnimation.setInterpolator(interpolator);
|
||||
mResetRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
|
||||
mResetRotateAnimation.setFillAfter(true);
|
||||
|
||||
}
|
||||
|
||||
public final boolean isVisible() {
|
||||
Animation currentAnim = getAnimation();
|
||||
if (null != currentAnim) {
|
||||
return mInAnim == currentAnim;
|
||||
}
|
||||
|
||||
return getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
startAnimation(mOutAnim);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
mArrowImageView.clearAnimation();
|
||||
startAnimation(mInAnim);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
if (animation == mOutAnim) {
|
||||
mArrowImageView.clearAnimation();
|
||||
setVisibility(View.GONE);
|
||||
} else if (animation == mInAnim) {
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
clearAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void releaseToRefresh() {
|
||||
mArrowImageView.startAnimation(mRotateAnimation);
|
||||
}
|
||||
|
||||
public void pullToRefresh() {
|
||||
mArrowImageView.startAnimation(mResetRotateAnimation);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.ILoadingLayout;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Orientation;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout {
|
||||
|
||||
static final String LOG_TAG = "PullToRefresh-LoadingLayout";
|
||||
|
||||
static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator();
|
||||
|
||||
private FrameLayout mInnerLayout;
|
||||
|
||||
protected final ImageView mHeaderImage;
|
||||
protected final ProgressBar mHeaderProgress;
|
||||
|
||||
private boolean mUseIntrinsicAnimation;
|
||||
|
||||
private final TextView mHeaderText;
|
||||
private final TextView mSubHeaderText;
|
||||
|
||||
protected final Mode mMode;
|
||||
protected final Orientation mScrollDirection;
|
||||
|
||||
private CharSequence mPullLabel;
|
||||
private CharSequence mRefreshingLabel;
|
||||
private CharSequence mReleaseLabel;
|
||||
|
||||
public LoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
|
||||
super(context);
|
||||
mMode = mode;
|
||||
mScrollDirection = scrollDirection;
|
||||
|
||||
switch (scrollDirection) {
|
||||
case HORIZONTAL:
|
||||
LayoutInflater.from(context).inflate(R.layout.view_pull_refresh_header_horizontal, this);
|
||||
break;
|
||||
case VERTICAL:
|
||||
default:
|
||||
LayoutInflater.from(context).inflate(R.layout.view_pull_refresh_header_vertical, this);
|
||||
break;
|
||||
}
|
||||
|
||||
mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
|
||||
mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text);
|
||||
mHeaderProgress = (ProgressBar) mInnerLayout.findViewById(R.id.pull_to_refresh_progress);
|
||||
mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text);
|
||||
mHeaderImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_image);
|
||||
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInnerLayout.getLayoutParams();
|
||||
|
||||
switch (mode) {
|
||||
case PULL_FROM_END:
|
||||
lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT;
|
||||
|
||||
// Load in labels
|
||||
mPullLabel = context.getString(R.string.pull_to_refresh_from_bottom_pull_label);
|
||||
mRefreshingLabel = context.getString(R.string.pull_to_refresh_from_bottom_refreshing_label);
|
||||
mReleaseLabel = context.getString(R.string.pull_to_refresh_from_bottom_release_label);
|
||||
break;
|
||||
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT;
|
||||
|
||||
// Load in labels
|
||||
mPullLabel = context.getString(R.string.pull_to_refresh_pull_label);
|
||||
mRefreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label);
|
||||
mReleaseLabel = context.getString(R.string.pull_to_refresh_release_label);
|
||||
break;
|
||||
}
|
||||
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) {
|
||||
Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground);
|
||||
if (null != background) {
|
||||
ViewCompat.setBackground(this, background);
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance)) {
|
||||
TypedValue styleID = new TypedValue();
|
||||
attrs.getValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance, styleID);
|
||||
setTextAppearance(styleID.data);
|
||||
}
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance)) {
|
||||
TypedValue styleID = new TypedValue();
|
||||
attrs.getValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance, styleID);
|
||||
setSubTextAppearance(styleID.data);
|
||||
}
|
||||
|
||||
// Text Color attrs need to be set after TextAppearance attrs
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextColor)) {
|
||||
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderTextColor);
|
||||
if (null != colors) {
|
||||
setTextColor(colors);
|
||||
}
|
||||
}
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderSubTextColor)) {
|
||||
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderSubTextColor);
|
||||
if (null != colors) {
|
||||
setSubTextColor(colors);
|
||||
}
|
||||
}
|
||||
|
||||
// Try and get defined drawable from Attrs
|
||||
Drawable imageDrawable = null;
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawable)) {
|
||||
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawable);
|
||||
}
|
||||
|
||||
// Check Specific Drawable from Attrs, these overrite the generic
|
||||
// drawable attr above
|
||||
switch (mode) {
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableStart)) {
|
||||
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableStart);
|
||||
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableTop)) {
|
||||
Utils.warnDeprecation("ptrDrawableTop", "ptrDrawableStart");
|
||||
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableTop);
|
||||
}
|
||||
break;
|
||||
|
||||
case PULL_FROM_END:
|
||||
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableEnd)) {
|
||||
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableEnd);
|
||||
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableBottom)) {
|
||||
Utils.warnDeprecation("ptrDrawableBottom", "ptrDrawableEnd");
|
||||
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableBottom);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If we don't have a user defined drawable, load the default
|
||||
if (null == imageDrawable) {
|
||||
imageDrawable = context.getResources().getDrawable(getDefaultDrawableResId());
|
||||
}
|
||||
|
||||
// Set Drawable, and save width/height
|
||||
setLoadingDrawable(imageDrawable);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public final void setHeight(int height) {
|
||||
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
|
||||
lp.height = height;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public final void setWidth(int width) {
|
||||
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
|
||||
lp.width = width;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public final int getContentSize() {
|
||||
switch (mScrollDirection) {
|
||||
case HORIZONTAL:
|
||||
return mInnerLayout.getWidth();
|
||||
case VERTICAL:
|
||||
default:
|
||||
return mInnerLayout.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public final void hideAllViews() {
|
||||
if (View.VISIBLE == mHeaderText.getVisibility()) {
|
||||
mHeaderText.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (View.VISIBLE == mHeaderProgress.getVisibility()) {
|
||||
mHeaderProgress.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (View.VISIBLE == mHeaderImage.getVisibility()) {
|
||||
mHeaderImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (View.VISIBLE == mSubHeaderText.getVisibility()) {
|
||||
mSubHeaderText.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public final void onPull(float scaleOfLayout) {
|
||||
if (!mUseIntrinsicAnimation) {
|
||||
onPullImpl(scaleOfLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public final void pullToRefresh() {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setText(mPullLabel);
|
||||
}
|
||||
|
||||
// Now call the callback
|
||||
pullToRefreshImpl();
|
||||
}
|
||||
|
||||
public final void refreshing() {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setText(mRefreshingLabel);
|
||||
}
|
||||
|
||||
if (mUseIntrinsicAnimation) {
|
||||
((AnimationDrawable) mHeaderImage.getDrawable()).start();
|
||||
} else {
|
||||
// Now call the callback
|
||||
refreshingImpl();
|
||||
}
|
||||
|
||||
if (null != mSubHeaderText) {
|
||||
mSubHeaderText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public final void releaseToRefresh() {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setText(mReleaseLabel);
|
||||
}
|
||||
|
||||
// Now call the callback
|
||||
releaseToRefreshImpl();
|
||||
}
|
||||
|
||||
public final void reset() {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setText(mPullLabel);
|
||||
}
|
||||
mHeaderImage.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mUseIntrinsicAnimation) {
|
||||
((AnimationDrawable) mHeaderImage.getDrawable()).stop();
|
||||
} else {
|
||||
// Now call the callback
|
||||
resetImpl();
|
||||
}
|
||||
|
||||
if (null != mSubHeaderText) {
|
||||
if (TextUtils.isEmpty(mSubHeaderText.getText())) {
|
||||
mSubHeaderText.setVisibility(View.GONE);
|
||||
} else {
|
||||
mSubHeaderText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastUpdatedLabel(CharSequence label) {
|
||||
setSubHeaderText(label);
|
||||
}
|
||||
|
||||
public final void setLoadingDrawable(Drawable imageDrawable) {
|
||||
// Set Drawable
|
||||
mHeaderImage.setImageDrawable(imageDrawable);
|
||||
mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable);
|
||||
|
||||
// Now call the callback
|
||||
onLoadingDrawableSet(imageDrawable);
|
||||
}
|
||||
|
||||
public void setPullLabel(CharSequence pullLabel) {
|
||||
mPullLabel = pullLabel;
|
||||
}
|
||||
|
||||
public void setRefreshingLabel(CharSequence refreshingLabel) {
|
||||
mRefreshingLabel = refreshingLabel;
|
||||
}
|
||||
|
||||
public void setReleaseLabel(CharSequence releaseLabel) {
|
||||
mReleaseLabel = releaseLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextTypeface(Typeface tf) {
|
||||
mHeaderText.setTypeface(tf);
|
||||
}
|
||||
|
||||
public final void showInvisibleViews() {
|
||||
if (View.INVISIBLE == mHeaderText.getVisibility()) {
|
||||
mHeaderText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (View.INVISIBLE == mHeaderProgress.getVisibility()) {
|
||||
mHeaderProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (View.INVISIBLE == mHeaderImage.getVisibility()) {
|
||||
mHeaderImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (View.INVISIBLE == mSubHeaderText.getVisibility()) {
|
||||
mSubHeaderText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for derivative Layouts
|
||||
*/
|
||||
|
||||
protected abstract int getDefaultDrawableResId();
|
||||
|
||||
protected abstract void onLoadingDrawableSet(Drawable imageDrawable);
|
||||
|
||||
protected abstract void onPullImpl(float scaleOfLayout);
|
||||
|
||||
protected abstract void pullToRefreshImpl();
|
||||
|
||||
protected abstract void refreshingImpl();
|
||||
|
||||
protected abstract void releaseToRefreshImpl();
|
||||
|
||||
protected abstract void resetImpl();
|
||||
|
||||
private void setSubHeaderText(CharSequence label) {
|
||||
if (null != mSubHeaderText) {
|
||||
if (TextUtils.isEmpty(label)) {
|
||||
mSubHeaderText.setVisibility(View.GONE);
|
||||
} else {
|
||||
mSubHeaderText.setText(label);
|
||||
|
||||
// Only set it to Visible if we're GONE, otherwise VISIBLE will
|
||||
// be set soon
|
||||
if (View.GONE == mSubHeaderText.getVisibility()) {
|
||||
mSubHeaderText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setSubTextAppearance(int value) {
|
||||
if (null != mSubHeaderText) {
|
||||
mSubHeaderText.setTextAppearance(getContext(), value);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSubTextColor(ColorStateList color) {
|
||||
if (null != mSubHeaderText) {
|
||||
mSubHeaderText.setTextColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTextAppearance(int value) {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setTextAppearance(getContext(), value);
|
||||
}
|
||||
if (null != mSubHeaderText) {
|
||||
mSubHeaderText.setTextAppearance(getContext(), value);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTextColor(ColorStateList color) {
|
||||
if (null != mHeaderText) {
|
||||
mHeaderText.setTextColor(color);
|
||||
}
|
||||
if (null != mSubHeaderText) {
|
||||
mSubHeaderText.setTextColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Mode;
|
||||
import com.zftlive.android.view.pulltorefresh.PullToRefreshBase.Orientation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
|
||||
|
||||
public class RotateLoadingLayout extends LoadingLayout {
|
||||
|
||||
static final int ROTATION_ANIMATION_DURATION = 1200;
|
||||
|
||||
private final Animation mRotateAnimation;
|
||||
private final Matrix mHeaderImageMatrix;
|
||||
|
||||
private float mRotationPivotX, mRotationPivotY;
|
||||
|
||||
private final boolean mRotateDrawableWhilePulling;
|
||||
|
||||
public RotateLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
|
||||
super(context, mode, scrollDirection, attrs);
|
||||
|
||||
mRotateDrawableWhilePulling = attrs.getBoolean(R.styleable.PullToRefresh_ptrRotateDrawableWhilePulling, true);
|
||||
|
||||
mHeaderImage.setScaleType(ScaleType.MATRIX);
|
||||
mHeaderImageMatrix = new Matrix();
|
||||
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
|
||||
|
||||
mRotateAnimation = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
|
||||
0.5f);
|
||||
mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
|
||||
mRotateAnimation.setDuration(ROTATION_ANIMATION_DURATION);
|
||||
mRotateAnimation.setRepeatCount(Animation.INFINITE);
|
||||
mRotateAnimation.setRepeatMode(Animation.RESTART);
|
||||
}
|
||||
|
||||
public void onLoadingDrawableSet(Drawable imageDrawable) {
|
||||
if (null != imageDrawable) {
|
||||
mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);
|
||||
mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPullImpl(float scaleOfLayout) {
|
||||
float angle;
|
||||
if (mRotateDrawableWhilePulling) {
|
||||
angle = scaleOfLayout * 90f;
|
||||
} else {
|
||||
angle = Math.max(0f, Math.min(180f, scaleOfLayout * 360f - 180f));
|
||||
}
|
||||
|
||||
mHeaderImageMatrix.setRotate(angle, mRotationPivotX, mRotationPivotY);
|
||||
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshingImpl() {
|
||||
mHeaderImage.startAnimation(mRotateAnimation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetImpl() {
|
||||
mHeaderImage.clearAnimation();
|
||||
resetImageRotation();
|
||||
}
|
||||
|
||||
private void resetImageRotation() {
|
||||
if (null != mHeaderImageMatrix) {
|
||||
mHeaderImageMatrix.reset();
|
||||
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pullToRefreshImpl() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseToRefreshImpl() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultDrawableResId() {
|
||||
return R.drawable.view_pull_refresh_default_ptr_rotate;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Utils {
|
||||
|
||||
static final String LOG_TAG = "PullToRefresh";
|
||||
|
||||
public static void warnDeprecation(String depreacted, String replacement) {
|
||||
Log.w(LOG_TAG, "You're using the deprecated " + depreacted + " attr, please switch over to " + replacement);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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 com.zftlive.android.view.pulltorefresh.internal;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.view.View;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ViewCompat {
|
||||
|
||||
public static void postOnAnimation(View view, Runnable runnable) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||
SDK16.postOnAnimation(view, runnable);
|
||||
} else {
|
||||
view.postDelayed(runnable, 16);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBackground(View view, Drawable background) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||
SDK16.setBackground(view, background);
|
||||
} else {
|
||||
view.setBackgroundDrawable(background);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLayerType(View view, int layerType) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
|
||||
SDK11.setLayerType(view, layerType);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
static class SDK11 {
|
||||
|
||||
public static void setLayerType(View view, int layerType) {
|
||||
view.setLayerType(layerType, null);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
static class SDK16 {
|
||||
|
||||
public static void postOnAnimation(View view, Runnable runnable) {
|
||||
view.postOnAnimation(runnable);
|
||||
}
|
||||
|
||||
public static void setBackground(View view, Drawable background) {
|
||||
view.setBackground(background);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package com.zftlive.android.view.webview;
|
||||
|
||||
import com.zftlive.android.tools.ToolAlert;
|
||||
import com.zftlive.android.view.webview.UIWebView.IOnReceivedTitle;
|
||||
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
public class UIWebChromeClient extends android.webkit.WebChromeClient {
|
||||
|
||||
private IOnReceivedTitle mIOnReceivedTitle;
|
||||
|
||||
private ProgressBar progressbar;
|
||||
|
||||
public UIWebChromeClient() {
|
||||
this(null,null);
|
||||
}
|
||||
|
||||
public UIWebChromeClient(IOnReceivedTitle mIOnReceivedTitle) {
|
||||
this(mIOnReceivedTitle,null);
|
||||
}
|
||||
|
||||
public UIWebChromeClient(ProgressBar progressbar) {
|
||||
this(null,progressbar);
|
||||
}
|
||||
|
||||
public UIWebChromeClient(IOnReceivedTitle mIOnReceivedTitle,ProgressBar progressbar) {
|
||||
this.mIOnReceivedTitle = mIOnReceivedTitle;
|
||||
this.progressbar = progressbar;
|
||||
}
|
||||
|
||||
/***页面加载进度**/
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress == 100) {
|
||||
if(null != progressbar){
|
||||
progressbar.setVisibility(View.GONE);
|
||||
}
|
||||
ToolAlert.closeLoading();
|
||||
} else {
|
||||
if(null != progressbar)
|
||||
{
|
||||
if (progressbar.getVisibility() == View.GONE){
|
||||
progressbar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
progressbar.setProgress(newProgress);
|
||||
}
|
||||
}
|
||||
super.onProgressChanged(view, newProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到网页标题回调函数
|
||||
*/
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
if(null != mIOnReceivedTitle){
|
||||
mIOnReceivedTitle.onReceivedTitle(title);
|
||||
}
|
||||
super.onReceivedTitle(view, title);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package com.zftlive.android.view.webview;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebSettings.RenderPriority;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
/**
|
||||
* 自定义浏览器
|
||||
* @author 曾繁添
|
||||
* @version 1.0
|
||||
*/
|
||||
public class UIWebView extends WebView {
|
||||
|
||||
private ProgressBar progressbar;
|
||||
|
||||
private boolean isShowProgress = true;
|
||||
|
||||
private static String htmlTitle = "";
|
||||
|
||||
private IOnReceivedTitle mIOnReceivedTitle = null;
|
||||
|
||||
/**
|
||||
* Construct a new WebView with a Context object.
|
||||
* @param context A Context object used to access application assets.
|
||||
*/
|
||||
public UIWebView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new WebView with layout parameters.
|
||||
* @param context A Context object used to access application assets.
|
||||
* @param attrs An AttributeSet passed to our parent.
|
||||
*/
|
||||
public UIWebView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.webViewStyle,true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new WebView with layout parameters and a default style.
|
||||
* @param context A Context object used to access application assets.
|
||||
* @param attrs An AttributeSet passed to our parent.
|
||||
* @param defStyle The default style resource ID.
|
||||
*/
|
||||
public UIWebView(Context context, AttributeSet attrs, int defStyle,boolean isShowProgress) {
|
||||
super(context, attrs, defStyle);
|
||||
//添加加载进度条
|
||||
if(isShowProgress){
|
||||
progressbar = new ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal);
|
||||
progressbar.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 5, 0, 0));
|
||||
addView(progressbar);
|
||||
}
|
||||
setWebChromeClient(new UIWebChromeClient(progressbar));
|
||||
//配置webView设置
|
||||
settingWebView();
|
||||
//设置滚动条样式
|
||||
setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
if(null != progressbar)
|
||||
{
|
||||
LayoutParams lp = (LayoutParams) progressbar.getLayoutParams();
|
||||
lp.x = l;
|
||||
lp.y = t;
|
||||
progressbar.setLayoutParams(lp);
|
||||
}
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint({ "NewApi", "SetJavaScriptEnabled" })
|
||||
private void settingWebView(){
|
||||
WebSettings settings = getSettings();
|
||||
//设置是否支持Javascript
|
||||
settings.setJavaScriptEnabled(true);
|
||||
//是否支持缩放
|
||||
settings.setSupportZoom(true);
|
||||
//settings.setBuiltInZoomControls(true);
|
||||
// if (Build.VERSION.SDK_INT >= 3.0)
|
||||
// settings.setDisplayZoomControls(false);
|
||||
//是否显示缩放按钮
|
||||
//settings.setDisplayZoomControls(false);
|
||||
//提高渲染优先级
|
||||
settings.setRenderPriority(RenderPriority.HIGH);
|
||||
//设置页面自适应手机屏幕
|
||||
settings.setUseWideViewPort(true);
|
||||
//WebView自适应屏幕大小
|
||||
settings.setLoadWithOverviewMode(true);
|
||||
//加载url前,设置不加载图片WebViewClient-->onPageFinished加载图片
|
||||
//settings.setBlockNetworkImage(true);
|
||||
//设置网页编码
|
||||
settings.setDefaultTextEncodingName("UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前网页Title
|
||||
* @param title 网页Title
|
||||
*/
|
||||
public static void setCurrentTitle(String title){
|
||||
htmlTitle = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前加载网页的标题
|
||||
* @return 网页<title>网页标题</title>
|
||||
*/
|
||||
public static String getCurrentTitle(){
|
||||
return htmlTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前加载网页的URL
|
||||
* @return 网页URL
|
||||
*/
|
||||
public static String getCurrentURL(){
|
||||
return UIWebViewClient.currentURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网页标题
|
||||
*
|
||||
*/
|
||||
public interface IOnReceivedTitle{
|
||||
public void onReceivedTitle(String title);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否显示进度条
|
||||
* @param isShow 是否显示
|
||||
*/
|
||||
public void isShowProgress(boolean isShow){
|
||||
this.isShowProgress = isShow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网页创建的progressbar
|
||||
* @return
|
||||
*/
|
||||
public ProgressBar getProgressBar(){
|
||||
return this.progressbar;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package com.zftlive.android.view.webview;
|
||||
|
||||
import com.zftlive.android.tools.ToolAlert;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.util.Log;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
/**
|
||||
* 自定义的浏览器
|
||||
* @author 曾繁添
|
||||
*
|
||||
*/
|
||||
public class UIWebViewClient extends WebViewClient {
|
||||
|
||||
/***跳转上下文**/
|
||||
private Context context;
|
||||
|
||||
/***跳转界面对象**/
|
||||
private Class target;
|
||||
|
||||
/***是否程序内部打开:true-->程序内部打开网页,false-->调用别的窗体打开**/
|
||||
private Boolean isInnerOpen = true;
|
||||
|
||||
/**过滤目标字符串**/
|
||||
private String filter = "";
|
||||
|
||||
private String TAG = UIWebViewClient.class.getSimpleName();
|
||||
|
||||
/**当前加载网页URL***/
|
||||
public static String currentURL = "";
|
||||
|
||||
/**
|
||||
* 默认内部打开链接
|
||||
*/
|
||||
public UIWebViewClient() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isInnerOpen 是否内部打开
|
||||
*/
|
||||
public UIWebViewClient(Boolean isInnerOpen) {
|
||||
this(null,null,isInnerOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param target 处理新开网页URL界面
|
||||
* @param isInnerOpen 是否内部打开
|
||||
*/
|
||||
public UIWebViewClient(Context context, Class target, Boolean isInnerOpen) {
|
||||
this(context,target,isInnerOpen,"");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param target 处理新开网页URL界面
|
||||
* @param isInnerOpen 是否内部打开
|
||||
* @param filter 过滤字符串
|
||||
*/
|
||||
public UIWebViewClient(Context context, Class target, Boolean isInnerOpen,String filter) {
|
||||
this.context = context;
|
||||
this.target = target;
|
||||
this.isInnerOpen = isInnerOpen;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/***
|
||||
* 让浏览器支持访问https请求
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler,
|
||||
SslError error) {
|
||||
handler.proceed();
|
||||
super.onReceivedSslError(view, handler, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制网页的链接跳转打开方式(拦截URL)
|
||||
*/
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
currentURL = url;
|
||||
if (isInnerOpen) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
} else {
|
||||
String host = Uri.parse(url).getHost();
|
||||
if (!host.startsWith("http")){
|
||||
host = "http://" + host;
|
||||
}
|
||||
if (!host.endsWith("/")){
|
||||
host = host + "/";
|
||||
}
|
||||
|
||||
if (null != context && null != target) {
|
||||
if (host.equals(filter)) {
|
||||
Intent intent = new Intent(context, target);
|
||||
intent.putExtra("url", url);
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
Log.e(TAG, "onPageStarted--->url="+url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
//加载完毕后,开始加载图片
|
||||
//view.getSettings().setBlockNetworkImage(false);
|
||||
|
||||
Log.e(TAG, "onPageFinished--->url="+url);
|
||||
super.onPageFinished(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode,String description, String failingUrl) {
|
||||
ToolAlert.closeLoading();
|
||||
ToolAlert.showShort("加载数据失败,错误码:"+errorCode+ "\n 原因描述:"+description);
|
||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
/**
|
||||
* @file XListView.java
|
||||
* @package me.maxwin.view
|
||||
* @create Mar 18, 2012 6:28:41 PM
|
||||
* @author Maxwin
|
||||
* @description An ListView support (a) Pull down to refresh, (b) Pull up to load more.
|
||||
* Implement IXListViewListener, and see stopRefresh() / stopLoadMore().
|
||||
*/
|
||||
package com.zftlive.android.view.xlistview;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.OnScrollListener;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Scroller;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class XListView extends ListView implements OnScrollListener {
|
||||
|
||||
private float mLastY = -1; // save event y
|
||||
private Scroller mScroller; // used for scroll back
|
||||
private OnScrollListener mScrollListener; // user's scroll listener
|
||||
|
||||
// the interface to trigger refresh and load more.
|
||||
private IXListViewListener mListViewListener;
|
||||
|
||||
// -- header view
|
||||
private XListViewHeader mHeaderView;
|
||||
// header view content, use it to calculate the Header's height. And hide it
|
||||
// when disable pull refresh.
|
||||
private RelativeLayout mHeaderViewContent;
|
||||
private TextView mHeaderTimeView;
|
||||
private int mHeaderViewHeight; // header view's height
|
||||
private boolean mEnablePullRefresh = true;
|
||||
private boolean mPullRefreshing = false; // is refreashing.
|
||||
|
||||
// -- footer view
|
||||
private XListViewFooter mFooterView;
|
||||
private boolean mEnablePullLoad;
|
||||
private boolean mPullLoading;
|
||||
private boolean mIsFooterReady = false;
|
||||
|
||||
// total list items, used to detect is at the bottom of listview.
|
||||
private int mTotalItemCount;
|
||||
|
||||
// for mScroller, scroll back from header or footer.
|
||||
private int mScrollBack;
|
||||
private final static int SCROLLBACK_HEADER = 0;
|
||||
private final static int SCROLLBACK_FOOTER = 1;
|
||||
|
||||
private final static int SCROLL_DURATION = 400; // scroll back duration
|
||||
private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px
|
||||
// at bottom, trigger
|
||||
// load more.
|
||||
private final static float OFFSET_RADIO = 1.8f; // support iOS like pull
|
||||
// feature.
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
public XListView(Context context) {
|
||||
super(context);
|
||||
initWithContext(context);
|
||||
}
|
||||
|
||||
public XListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initWithContext(context);
|
||||
}
|
||||
|
||||
public XListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initWithContext(context);
|
||||
}
|
||||
|
||||
private void initWithContext(Context context) {
|
||||
mScroller = new Scroller(context, new DecelerateInterpolator());
|
||||
// XListView need the scroll event, and it will dispatch the event to
|
||||
// user's listener (as a proxy).
|
||||
super.setOnScrollListener(this);
|
||||
|
||||
// init header view
|
||||
mHeaderView = new XListViewHeader(context);
|
||||
mHeaderViewContent = (RelativeLayout) mHeaderView
|
||||
.findViewById(R.id.xlistview_header_content);
|
||||
mHeaderTimeView = (TextView) mHeaderView
|
||||
.findViewById(R.id.xlistview_header_time);
|
||||
addHeaderView(mHeaderView);
|
||||
|
||||
// init footer view
|
||||
mFooterView = new XListViewFooter(context);
|
||||
|
||||
// init header height
|
||||
mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
mHeaderViewHeight = mHeaderViewContent.getHeight();
|
||||
getViewTreeObserver()
|
||||
.removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
// make sure XListViewFooter is the last footer view, and only add once.
|
||||
if (mIsFooterReady == false) {
|
||||
mIsFooterReady = true;
|
||||
addFooterView(mFooterView);
|
||||
}
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* enable or disable pull down refresh feature.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
public void setPullRefreshEnable(boolean enable) {
|
||||
mEnablePullRefresh = enable;
|
||||
if (!mEnablePullRefresh) { // disable, hide the content
|
||||
mHeaderViewContent.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
mHeaderViewContent.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* enable or disable pull up load more feature.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
public void setPullLoadEnable(boolean enable) {
|
||||
mEnablePullLoad = enable;
|
||||
if (!mEnablePullLoad) {
|
||||
mFooterView.hide();
|
||||
mFooterView.setOnClickListener(null);
|
||||
} else {
|
||||
mPullLoading = false;
|
||||
mFooterView.show();
|
||||
mFooterView.setState(XListViewFooter.STATE_NORMAL);
|
||||
// both "pull up" and "click" will invoke load more.
|
||||
mFooterView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startLoadMore();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop refresh, reset header view.
|
||||
*/
|
||||
public void stopRefresh() {
|
||||
if (mPullRefreshing == true) {
|
||||
mPullRefreshing = false;
|
||||
resetHeaderHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop load more, reset footer view.
|
||||
*/
|
||||
public void stopLoadMore() {
|
||||
if (mPullLoading == true) {
|
||||
mPullLoading = false;
|
||||
mFooterView.setState(XListViewFooter.STATE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set last refresh time
|
||||
*
|
||||
* @param time
|
||||
*/
|
||||
public void setRefreshTime(String time) {
|
||||
mHeaderTimeView.setText(time);
|
||||
}
|
||||
|
||||
private void invokeOnScrolling() {
|
||||
if (mScrollListener instanceof OnXScrollListener) {
|
||||
OnXScrollListener l = (OnXScrollListener) mScrollListener;
|
||||
l.onXScrolling(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHeaderHeight(float delta) {
|
||||
mHeaderView.setVisiableHeight((int) delta
|
||||
+ mHeaderView.getVisiableHeight());
|
||||
if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头
|
||||
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
|
||||
mHeaderView.setState(XListViewHeader.STATE_READY);
|
||||
} else {
|
||||
mHeaderView.setState(XListViewHeader.STATE_NORMAL);
|
||||
}
|
||||
}
|
||||
setSelection(0); // scroll to top each time
|
||||
}
|
||||
|
||||
/**
|
||||
* reset header view's height.
|
||||
*/
|
||||
private void resetHeaderHeight() {
|
||||
int height = mHeaderView.getVisiableHeight();
|
||||
if (height == 0) // not visible.
|
||||
return;
|
||||
// refreshing and header isn't shown fully. do nothing.
|
||||
if (mPullRefreshing && height <= mHeaderViewHeight) {
|
||||
return;
|
||||
}
|
||||
int finalHeight = 0; // default: scroll back to dismiss header.
|
||||
// is refreshing, just scroll back to show all the header.
|
||||
if (mPullRefreshing && height > mHeaderViewHeight) {
|
||||
finalHeight = mHeaderViewHeight;
|
||||
}
|
||||
mScrollBack = SCROLLBACK_HEADER;
|
||||
mScroller.startScroll(0, height, 0, finalHeight - height,
|
||||
SCROLL_DURATION);
|
||||
// trigger computeScroll
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void updateFooterHeight(float delta) {
|
||||
int height = mFooterView.getBottomMargin() + (int) delta;
|
||||
if (mEnablePullLoad && !mPullLoading) {
|
||||
if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load
|
||||
// more.
|
||||
mFooterView.setState(XListViewFooter.STATE_READY);
|
||||
} else {
|
||||
mFooterView.setState(XListViewFooter.STATE_NORMAL);
|
||||
}
|
||||
}
|
||||
mFooterView.setBottomMargin(height);
|
||||
|
||||
// setSelection(mTotalItemCount - 1); // scroll to bottom
|
||||
}
|
||||
|
||||
private void resetFooterHeight() {
|
||||
int bottomMargin = mFooterView.getBottomMargin();
|
||||
if (bottomMargin > 0) {
|
||||
mScrollBack = SCROLLBACK_FOOTER;
|
||||
mScroller.startScroll(0, bottomMargin, 0, -bottomMargin,
|
||||
SCROLL_DURATION);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoadMore() {
|
||||
mPullLoading = true;
|
||||
mFooterView.setState(XListViewFooter.STATE_LOADING);
|
||||
if (mListViewListener != null) {
|
||||
mListViewListener.onLoadMore();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mLastY == -1) {
|
||||
mLastY = ev.getRawY();
|
||||
}
|
||||
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mLastY = ev.getRawY();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float deltaY = ev.getRawY() - mLastY;
|
||||
mLastY = ev.getRawY();
|
||||
if (getFirstVisiblePosition() == 0
|
||||
&& (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
|
||||
// the first item is showing, header has shown or pull down.
|
||||
updateHeaderHeight(deltaY / OFFSET_RADIO);
|
||||
invokeOnScrolling();
|
||||
} else if (getLastVisiblePosition() == mTotalItemCount - 1
|
||||
&& (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {
|
||||
// last item, already pulled up or want to pull up.
|
||||
updateFooterHeight(-deltaY / OFFSET_RADIO);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLastY = -1; // reset
|
||||
if (getFirstVisiblePosition() == 0) {
|
||||
// invoke refresh
|
||||
if (mEnablePullRefresh
|
||||
&& mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
|
||||
mPullRefreshing = true;
|
||||
mHeaderView.setState(XListViewHeader.STATE_REFRESHING);
|
||||
if (mListViewListener != null) {
|
||||
mListViewListener.onRefresh();
|
||||
}
|
||||
}
|
||||
resetHeaderHeight();
|
||||
} else if (getLastVisiblePosition() == mTotalItemCount - 1) {
|
||||
// invoke load more.
|
||||
if (mEnablePullLoad
|
||||
&& mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA
|
||||
&& !mPullLoading) {
|
||||
startLoadMore();
|
||||
}
|
||||
resetFooterHeight();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
if (mScrollBack == SCROLLBACK_HEADER) {
|
||||
mHeaderView.setVisiableHeight(mScroller.getCurrY());
|
||||
} else {
|
||||
mFooterView.setBottomMargin(mScroller.getCurrY());
|
||||
}
|
||||
postInvalidate();
|
||||
invokeOnScrolling();
|
||||
}
|
||||
super.computeScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnScrollListener(OnScrollListener l) {
|
||||
mScrollListener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
if (mScrollListener != null) {
|
||||
mScrollListener.onScrollStateChanged(view, scrollState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem,
|
||||
int visibleItemCount, int totalItemCount) {
|
||||
// send to user's listener
|
||||
mTotalItemCount = totalItemCount;
|
||||
if (mScrollListener != null) {
|
||||
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,
|
||||
totalItemCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void setXListViewListener(IXListViewListener l) {
|
||||
mListViewListener = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* you can listen ListView.OnScrollListener or this one. it will invoke
|
||||
* onXScrolling when header/footer scroll back.
|
||||
*/
|
||||
public interface OnXScrollListener extends OnScrollListener {
|
||||
public void onXScrolling(View view);
|
||||
}
|
||||
|
||||
/**
|
||||
* implements this interface to get refresh/load more event.
|
||||
*/
|
||||
public interface IXListViewListener {
|
||||
public void onRefresh();
|
||||
|
||||
public void onLoadMore();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @file XFooterView.java
|
||||
* @create Mar 31, 2012 9:33:43 PM
|
||||
* @author Maxwin
|
||||
* @description XListView's footer
|
||||
*/
|
||||
package com.zftlive.android.view.xlistview;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class XListViewFooter extends LinearLayout {
|
||||
public final static int STATE_NORMAL = 0;
|
||||
public final static int STATE_READY = 1;
|
||||
public final static int STATE_LOADING = 2;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private View mContentView;
|
||||
private View mProgressBar;
|
||||
private TextView mHintView;
|
||||
|
||||
public XListViewFooter(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public XListViewFooter(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
|
||||
public void setState(int state) {
|
||||
mHintView.setVisibility(View.INVISIBLE);
|
||||
mProgressBar.setVisibility(View.INVISIBLE);
|
||||
mHintView.setVisibility(View.INVISIBLE);
|
||||
if (state == STATE_READY) {
|
||||
mHintView.setVisibility(View.VISIBLE);
|
||||
mHintView.setText(R.string.xlistview_footer_hint_ready);
|
||||
} else if (state == STATE_LOADING) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mHintView.setVisibility(View.VISIBLE);
|
||||
mHintView.setText(R.string.xlistview_footer_hint_normal);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBottomMargin(int height) {
|
||||
if (height < 0) return ;
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();
|
||||
lp.bottomMargin = height;
|
||||
mContentView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
public int getBottomMargin() {
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();
|
||||
return lp.bottomMargin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* normal status
|
||||
*/
|
||||
public void normal() {
|
||||
mHintView.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* loading status
|
||||
*/
|
||||
public void loading() {
|
||||
mHintView.setVisibility(View.GONE);
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* hide footer when disable pull load more
|
||||
*/
|
||||
public void hide() {
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();
|
||||
lp.height = 0;
|
||||
mContentView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* show footer
|
||||
*/
|
||||
public void show() {
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();
|
||||
lp.height = LayoutParams.WRAP_CONTENT;
|
||||
mContentView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private void initView(Context context) {
|
||||
mContext = context;
|
||||
LinearLayout moreView = (LinearLayout)LayoutInflater.from(mContext).inflate(R.layout.view_xlistview_footer, null);
|
||||
addView(moreView);
|
||||
moreView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
mContentView = moreView.findViewById(R.id.xlistview_footer_content);
|
||||
mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);
|
||||
mHintView = (TextView)moreView.findViewById(R.id.xlistview_footer_hint_textview);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @file XListViewHeader.java
|
||||
* @create Apr 18, 2012 5:22:27 PM
|
||||
* @author Maxwin
|
||||
* @description XListView's header
|
||||
*/
|
||||
package com.zftlive.android.view.xlistview;
|
||||
|
||||
import com.zftlive.android.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class XListViewHeader extends LinearLayout {
|
||||
private LinearLayout mContainer;
|
||||
private ImageView mArrowImageView;
|
||||
private ProgressBar mProgressBar;
|
||||
private TextView mHintTextView;
|
||||
private int mState = STATE_NORMAL;
|
||||
|
||||
private Animation mRotateUpAnim;
|
||||
private Animation mRotateDownAnim;
|
||||
|
||||
private final int ROTATE_ANIM_DURATION = 180;
|
||||
|
||||
public final static int STATE_NORMAL = 0;
|
||||
public final static int STATE_READY = 1;
|
||||
public final static int STATE_REFRESHING = 2;
|
||||
|
||||
public XListViewHeader(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
public XListViewHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
private void initView(Context context) {
|
||||
// 初始情况,设置下拉刷新view高度为0
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
LayoutParams.FILL_PARENT, 0);
|
||||
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(
|
||||
R.layout.view_xlistview_header, null);
|
||||
addView(mContainer, lp);
|
||||
setGravity(Gravity.BOTTOM);
|
||||
|
||||
mArrowImageView = (ImageView)findViewById(R.id.xlistview_header_arrow);
|
||||
mHintTextView = (TextView)findViewById(R.id.xlistview_header_hint_textview);
|
||||
mProgressBar = (ProgressBar)findViewById(R.id.xlistview_header_progressbar);
|
||||
|
||||
mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
|
||||
0.5f);
|
||||
mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
|
||||
mRotateUpAnim.setFillAfter(true);
|
||||
mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
|
||||
0.5f);
|
||||
mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
|
||||
mRotateDownAnim.setFillAfter(true);
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
if (state == mState) return ;
|
||||
|
||||
if (state == STATE_REFRESHING) { // 显示进度
|
||||
mArrowImageView.clearAnimation();
|
||||
mArrowImageView.setVisibility(View.INVISIBLE);
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
} else { // 显示箭头图片
|
||||
mArrowImageView.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
switch(state){
|
||||
case STATE_NORMAL:
|
||||
if (mState == STATE_READY) {
|
||||
mArrowImageView.startAnimation(mRotateDownAnim);
|
||||
}
|
||||
if (mState == STATE_REFRESHING) {
|
||||
mArrowImageView.clearAnimation();
|
||||
}
|
||||
mHintTextView.setText(R.string.xlistview_header_hint_normal);
|
||||
break;
|
||||
case STATE_READY:
|
||||
if (mState != STATE_READY) {
|
||||
mArrowImageView.clearAnimation();
|
||||
mArrowImageView.startAnimation(mRotateUpAnim);
|
||||
mHintTextView.setText(R.string.xlistview_header_hint_ready);
|
||||
}
|
||||
break;
|
||||
case STATE_REFRESHING:
|
||||
mHintTextView.setText(R.string.xlistview_header_hint_loading);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
mState = state;
|
||||
}
|
||||
|
||||
public void setVisiableHeight(int height) {
|
||||
if (height < 0)
|
||||
height = 0;
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer
|
||||
.getLayoutParams();
|
||||
lp.height = height;
|
||||
mContainer.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
public int getVisiableHeight() {
|
||||
return mContainer.getHeight();
|
||||
}
|
||||
|
||||
}
|